diff --git a/src/Gui/Stylesheets/FreeCAD.qss b/src/Gui/Stylesheets/FreeCAD.qss
index 3cf0742e3d..9054f55c8b 100644
--- a/src/Gui/Stylesheets/FreeCAD.qss
+++ b/src/Gui/Stylesheets/FreeCAD.qss
@@ -922,6 +922,12 @@ QToolBar::separator:vertical {
background-color: @GeneralBorderColor;
}
+MRichTextEdit[floating="true"] {
+ border: 1px solid @GeneralBorderColor;
+ border-radius: 2px;
+ padding: 1px;
+}
+
/*The "show more" button (it can also be stylable with "QToolBarExtension" icon is not working
Qproperty works but breaks when you move the toolbar*/
diff --git a/src/Mod/AddonManager b/src/Mod/AddonManager
index 937b687723..d9c593594a 160000
--- a/src/Mod/AddonManager
+++ b/src/Mod/AddonManager
@@ -1 +1 @@
-Subproject commit 937b6877239dc78ef59eeefe8099e5f14243eda1
+Subproject commit d9c593594ae4187d09b3ec9c7989db6c3a22d7a2
diff --git a/src/Mod/TechDraw/App/DrawRichAnno.cpp b/src/Mod/TechDraw/App/DrawRichAnno.cpp
index 755796f6b3..b9339dcd76 100644
--- a/src/Mod/TechDraw/App/DrawRichAnno.cpp
+++ b/src/Mod/TechDraw/App/DrawRichAnno.cpp
@@ -20,6 +20,7 @@
* *
***************************************************************************/
+#include
#include "DrawRichAnno.h"
#include "DrawRichAnnoPy.h" // generated from DrawRichAnnoPy.xml
@@ -41,6 +42,8 @@ DrawRichAnno::DrawRichAnno()
"Object to which this annontation is attached");
ADD_PROPERTY_TYPE(AnnoText, (""), group, App::Prop_None, "Annotation text");
ADD_PROPERTY_TYPE(ShowFrame, (true), group, App::Prop_None, "Outline rectangle on/off");
+ // Necessary to support legacy files made before #24624.
+ ADD_PROPERTY_TYPE(OriginCentered, (false), group, App::Prop_None, "Center the annotation on it's origin.");
ADD_PROPERTY_TYPE(MaxWidth, (-1.0), group, App::Prop_None, "Width limit before auto wrap");
Caption.setStatus(App::Property::Hidden, true);
Scale.setStatus(App::Property::Hidden, true);
@@ -48,6 +51,40 @@ DrawRichAnno::DrawRichAnno()
}
+void DrawRichAnno::Restore(Base::XMLReader& reader)
+{
+ bool originCenteredFound = false;
+
+ // Start parsing the properties block.
+ reader.readElement("Properties");
+ int propCount = reader.getAttribute("Count");
+
+ for (int i = 0; i < propCount; i++) {
+ reader.readElement("Property");
+ const char* propName = reader.getAttribute("name");
+
+ // The "checking" part:
+ if (strcmp(propName, "OriginCentered") == 0) {
+ originCenteredFound = true;
+ }
+
+ // The "restoring" part:
+ App::Property* prop = getPropertyByName(propName);
+ if (prop) {
+ prop->Restore(reader); // Restore the value
+ }
+
+ reader.readEndElement("Property");
+ }
+
+ reader.readEndElement("Properties");
+
+ // Ensure backward compatibility: Old files have their anno centered on origin.
+ if (!originCenteredFound) {
+ OriginCentered.setValue(true);
+ }
+}
+
void DrawRichAnno::onChanged(const App::Property* prop)
{
if (!isRestoring()) {
diff --git a/src/Mod/TechDraw/App/DrawRichAnno.h b/src/Mod/TechDraw/App/DrawRichAnno.h
index 199c20c791..b380ecb7e7 100644
--- a/src/Mod/TechDraw/App/DrawRichAnno.h
+++ b/src/Mod/TechDraw/App/DrawRichAnno.h
@@ -44,7 +44,9 @@ public:
App::PropertyString AnnoText;
App::PropertyBool ShowFrame;
App::PropertyFloat MaxWidth;
+ App::PropertyBool OriginCentered;
+ void Restore(Base::XMLReader& reader) override;
short mustExecute() const override;
App::DocumentObjectExecReturn *execute() override;
diff --git a/src/Mod/TechDraw/Gui/CommandAnnotate.cpp b/src/Mod/TechDraw/Gui/CommandAnnotate.cpp
index dd99af44c5..bad39a35e9 100644
--- a/src/Mod/TechDraw/Gui/CommandAnnotate.cpp
+++ b/src/Mod/TechDraw/Gui/CommandAnnotate.cpp
@@ -150,7 +150,7 @@ CmdTechDrawRichTextAnnotation::CmdTechDrawRichTextAnnotation()
sToolTipText = QT_TR_NOOP("Inserts a rich text annotation in the current page");
sWhatsThis = "TechDraw_RichTextAnnotation";
sStatusTip = sToolTipText;
- sPixmap = "actions/TechDraw_RichTextAnnotation";
+ sPixmap = "actions/TechDraw_Annotation";
}
void CmdTechDrawRichTextAnnotation::activated(int iMsg)
diff --git a/src/Mod/TechDraw/Gui/QGCustomText.cpp b/src/Mod/TechDraw/Gui/QGCustomText.cpp
index e1a057d652..814d65fc40 100644
--- a/src/Mod/TechDraw/Gui/QGCustomText.cpp
+++ b/src/Mod/TechDraw/Gui/QGCustomText.cpp
@@ -26,6 +26,9 @@
# include
# include
# include
+#include
+#include
+#include
#include
#include
@@ -167,6 +170,8 @@ void QGCustomText::setTightBounding(bool tight)
void QGCustomText::paint ( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget) {
QStyleOptionGraphicsItem myOption(*option);
+ // Remove HasFocus state to prevent the dashed rectangle from being drawn
+ myOption.state &= ~QStyle::State_HasFocus;
myOption.state &= ~QStyle::State_Selected;
// painter->setPen(Qt::green);
@@ -269,3 +274,78 @@ void QGCustomText::makeMark(Base::Vector3d v)
makeMark(v.x, v.y);
}
+void QGCustomText::focusInEvent(QFocusEvent* event)
+{
+ // Store the initial cursor state when the item gains focus
+ m_lastCursor = textCursor();
+ QGraphicsTextItem::focusInEvent(event);
+}
+
+void QGCustomText::keyPressEvent(QKeyEvent* event)
+{
+ // If the user returns more than once, then the format (the font
+ // size we set as default) is lost. So we need to handle manually
+ if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
+ QTextCursor cursor = textCursor();
+
+ // 1. Preserve the all-important character format from the current cursor position.
+ QTextCharFormat formatToPreserve = cursor.charFormat();
+
+ // 2. Preserve the block format (for things like alignment, indentation).
+ QTextBlockFormat blockFormatToPreserve = cursor.blockFormat();
+
+ // 3. Manually insert a new block (a newline).
+ // This automatically deletes any selected text, which is the correct behavior.
+ cursor.insertBlock(blockFormatToPreserve, formatToPreserve);
+
+ // 4. After inserting the block, the cursor is at the start of the new line.
+ // Its charFormat should already be correct because we passed it to insertBlock.
+ // We don't need to do anything further with the cursor.
+
+ // 5. Explicitly apply the cursor back to the item to ensure the view updates.
+ setTextCursor(cursor);
+
+ // 6. Notify that the selection/cursor has changed.
+ checkCursorChange();
+
+ // 7. Accept the event to stop it from being processed further.
+ event->accept();
+ return;
+ }
+
+ // Let the base class handle the key press first (which moves the cursor)
+ QGraphicsTextItem::keyPressEvent(event);
+ // Now check if the cursor or selection changed as a result
+ checkCursorChange();
+}
+
+void QGCustomText::mousePressEvent(QGraphicsSceneMouseEvent* event)
+{
+ // Let the base class handle the mouse press first
+ QGraphicsTextItem::mousePressEvent(event);
+ checkCursorChange();
+}
+
+void QGCustomText::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
+{
+ // Let the base class handle the mouse release first
+ QGraphicsTextItem::mouseReleaseEvent(event);
+ checkCursorChange();
+}
+
+void QGCustomText::checkCursorChange()
+{
+ QTextCursor currentCursor = textCursor();
+
+ // Compare the properties of the cursors that define selection and position.
+ if (currentCursor.position() != m_lastCursor.position()
+ || currentCursor.anchor() != m_lastCursor.anchor()) {
+ // The anchor and position define the selection. If either has changed,
+ // the selection/cursor has changed.
+ Q_EMIT selectionChanged();
+ }
+
+ m_lastCursor = currentCursor;
+}
+
+#include
diff --git a/src/Mod/TechDraw/Gui/QGCustomText.h b/src/Mod/TechDraw/Gui/QGCustomText.h
index f0b48ef649..1a2983e6d3 100644
--- a/src/Mod/TechDraw/Gui/QGCustomText.h
+++ b/src/Mod/TechDraw/Gui/QGCustomText.h
@@ -27,6 +27,7 @@
#include
#include
#include
+#include
QT_BEGIN_NAMESPACE
class QPainter;
@@ -43,6 +44,7 @@ namespace TechDrawGui
class TechDrawGuiExport QGCustomText : public QGraphicsTextItem
{
+ Q_OBJECT
public:
explicit QGCustomText(QGraphicsItem* parent = nullptr);
~QGCustomText() override {}
@@ -81,7 +83,14 @@ public:
void makeMark(double x, double y);
void makeMark(Base::Vector3d v);
+Q_SIGNALS:
+ void selectionChanged();
+
protected:
+ void keyPressEvent(QKeyEvent* event) override;
+ void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override;
+ void focusInEvent(QFocusEvent* event) override;
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
@@ -92,6 +101,8 @@ protected:
QColor m_colNormal;
private:
+ void checkCursorChange();
+ QTextCursor m_lastCursor;
};
diff --git a/src/Mod/TechDraw/Gui/QGIRichAnno.cpp b/src/Mod/TechDraw/Gui/QGIRichAnno.cpp
index 49658f1799..c534ec77ed 100644
--- a/src/Mod/TechDraw/Gui/QGIRichAnno.cpp
+++ b/src/Mod/TechDraw/Gui/QGIRichAnno.cpp
@@ -22,17 +22,27 @@
# include
+# include
# include
# include
# include
# include
# include
+# include
+# include
# include
# include
# include
+# include
# include
+# include
+# include
+# include
#include
+#include
+#include
+
#include
#include
@@ -51,13 +61,26 @@ using namespace TechDraw;
using namespace TechDrawGui;
using DU = DrawUtil;
+const double QGIRichAnno::HandleInteractionMargin = 30.0; // Scene units for hover/click
+const double QGIRichAnno::MinTextWidthDocument = 5.0; // Min width in document units
//**************************************************************
QGIRichAnno::QGIRichAnno() :
- m_isExportingPdf(false), m_isExportingSvg(false), m_hasHover(false)
+ m_isExportingPdf(false),
+ m_isExportingSvg(false),
+ m_currentResizeHandle(ResizeHandle::NoHandle),
+ m_isResizing(false),
+ m_isDraggingMidResize(false),
+ m_transactionOpen(false),
+ m_dragStartMouseScenePos(),
+ m_initialItemScenePos(),
+ m_initialTextWidthScene(0.0),
+ m_isEditing(false),
+ m_textScaleFactor(1.0),
+ m_lastGoodWidthScene(0.0)
{
setHandlesChildEvents(false);
- setAcceptHoverEvents(false);
+ setAcceptHoverEvents(true); // Enable hover events for cursor changes
setFlag(QGraphicsItem::ItemIsSelectable, true);
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemSendsScenePositionChanges, true);
@@ -68,15 +91,18 @@ QGIRichAnno::QGIRichAnno() :
m_text->setDefaultTextColor(PreferencesGui::normalQColor());
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);
+ connect(m_text->document(),
+ &QTextDocument::contentsChanged,
+ this,
+ &QGIRichAnno::onContentsChanged);
+ connect(m_text, &QGCustomText::selectionChanged, this, &QGIRichAnno::selectionChanged);
}
void QGIRichAnno::updateView(bool update)
@@ -100,24 +126,15 @@ void QGIRichAnno::updateView(bool update)
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);
- }
+ // Convert the word processing font size spec (in typographic points) to scene units for
+ // the screen or pdf rendering
+ constexpr double mmPerPoint {25.4 / 72}; // mm/in / points/inch
+ m_textScaleFactor = Rez::getRezFactor() * mmPerPoint; // scene units per point: 3.53
+ m_text->setScale(m_textScaleFactor);
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());
@@ -146,45 +163,53 @@ 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);
+ updateLayout();
- //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());
+ // convert the text size
+ if (!m_isEditing) {
+ m_text->setHtml(QString::fromUtf8(annoFeat->AnnoText.getValue()));
}
- m_text->setHtml(outHtml);
+
+ // 1. Get the bounding rectangle of the text in its own local coordinates.
+ QRectF textParentRect = m_text->mapRectToParent(m_text->boundingRect());
+
+ QPointF offset(0.0, 0.0);
+ if (annoFeat->OriginCentered.getValue()) {
+ offset = QPointF(-textParentRect.width() / 2.0, -textParentRect.height() / 2.0);
+ }
+ m_text->setPos(offset);
+ textParentRect = m_text->mapRectToParent(m_text->boundingRect());
+
+ if (annoFeat->OriginCentered.getValue() || !getExportingSvg()) {
+ m_rect->setRect(textParentRect);
+ }
+
+ m_rect->setPen(rectPen());
+ m_rect->setBrush(Qt::NoBrush);
+ m_rect->setVisible(annoFeat->ShowFrame.getValue());
+
if (getExportingSvg()) {
+ // Convert the word processing font size spec (in typographic points) to CSS pixels
+ // for Svg rendering
+ constexpr double mmPerPoint {25.4 / 72};
+ constexpr double cssPxPerPoint {16 / 12}; // CSS says 12 pt text is 16 px high
+ m_text->setScale(cssPxPerPoint);
+
+ // QSvgRenderer places the text's top edge flush with the item's origin.
+ // We must manually shift the QGraphicsTextItem down to create padding.
+ QTextBlock firstBlock = m_text->document()->begin();
+ if (firstBlock.isValid()) {
+ QTextCursor cursor(firstBlock);
+ double fontSizePx = cursor.charFormat().fontPointSize();
+ m_text->setY(m_text->pos().y() + (fontSizePx * mmPerPoint));
+ }
+
// 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();
+ if (m_isEditing) {
+ Q_EMIT positionChanged(scenePos());
}
}
@@ -228,53 +253,6 @@ void QGIRichAnno::setLineSpacing(int lineSpacing)
}
}
-//! 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(QStringLiteral("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()
{
return static_cast(getViewObject());
@@ -284,10 +262,7 @@ TechDraw::DrawRichAnno* QGIRichAnno::getFeature()
// 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 };
+ return childrenBoundingRect();
}
void QGIRichAnno::paint ( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget) {
@@ -327,35 +302,368 @@ QFont QGIRichAnno::prefFont()
return PreferencesGui::labelFontQFont();
}
-void QGIRichAnno::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) {
- Q_UNUSED(event);
-
- TechDraw::DrawRichAnno *annotation = dynamic_cast(getViewObject());
- if (!annotation)
+void QGIRichAnno::hoverMoveEvent(QGraphicsSceneHoverEvent* event)
+{
+ TechDraw::DrawRichAnno* annoFeat = getFeature();
+ if (!annoFeat || annoFeat->isLocked()) {
+ setCursor(Qt::ArrowCursor);
+ QGIView::hoverMoveEvent(event);
return;
+ }
+ // Mouse position in QGIRichAnno's local coordinates
+ QPointF localPos = mapFromScene(event->scenePos());
- QString text = QString::fromUtf8(annotation->AnnoText.getValue());
+ // Calculate visual edges of m_rect in QGIRichAnno's local coordinates
+ double visualRectLeftEdgeX = m_rect->x() + m_rect->rect().left();
+ double visualRectRightEdgeX = m_rect->x() + m_rect->rect().right();
- QDialog dialog(nullptr);
- dialog.setWindowTitle(QObject::tr("Rich text editor"));
- dialog.setMinimumWidth(400);
- dialog.setMinimumHeight(400);
+ bool onLeftEdge = qAbs(localPos.x() - visualRectLeftEdgeX) < HandleInteractionMargin;
+ bool onRightEdge = qAbs(localPos.x() - visualRectRightEdgeX) < HandleInteractionMargin;
- MRichTextEdit richEdit(&dialog, text);
- QGridLayout gridLayout(&dialog);
- gridLayout.addWidget(&richEdit, 0, 0, 1, 1);
+ if (onLeftEdge || onRightEdge) {
+ setCursor(Qt::SizeHorCursor);
+ }
+ else {
+ setCursor(Qt::ArrowCursor);
+ }
+ QGIView::hoverMoveEvent(event);
+}
- connect(&richEdit, &MRichTextEdit::saveText, &dialog, &QDialog::accept);
- connect(&richEdit, &MRichTextEdit::editorFinished, &dialog, &QDialog::reject);
+void QGIRichAnno::mousePressEvent(QGraphicsSceneMouseEvent* event)
+{
+ TechDraw::DrawRichAnno* annoFeat = getFeature();
+ // Allow resizing even if MaxWidth is initially -1 or 0, as long as frame is shown
+ if (event->button() != Qt::LeftButton || !annoFeat || annoFeat->isLocked()) {
+ QGIView::mousePressEvent(event);
+ return;
+ }
- 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();
+ // Mouse position in QGIRichAnno's local coordinates
+ QPointF localPos = mapFromScene(event->scenePos());
+ m_currentResizeHandle = ResizeHandle::NoHandle;
+
+ // Calculate visual edges of m_rect in QGIRichAnno's local coordinates
+ // m_rect's geometry should be up-to-date from the last draw/updateView
+ double visualRectLeftEdgeX = m_rect->x() + m_rect->rect().left();
+ double visualRectRightEdgeX = m_rect->x() + m_rect->rect().right();
+
+ if (qAbs(localPos.x() - visualRectLeftEdgeX) < HandleInteractionMargin) {
+ m_currentResizeHandle = ResizeHandle::LeftHandle;
+ }
+ else if (qAbs(localPos.x() - visualRectRightEdgeX) < HandleInteractionMargin) {
+ m_currentResizeHandle = ResizeHandle::RightHandle;
+ }
+
+ if (m_currentResizeHandle != ResizeHandle::NoHandle) {
+ m_isResizing = true;
+ m_isDraggingMidResize = false;
+ m_transactionOpen = false;
+ m_dragStartMouseScenePos = event->scenePos();
+ m_initialItemScenePos = this->scenePos();
+
+ // Determine initial text width for resizing
+ if (annoFeat->MaxWidth.getValue() > 0.0) {
+ m_initialTextWidthScene = Rez::guiX(annoFeat->MaxWidth.getValue());
}
+ else {
+ if (m_rect) {
+ m_initialTextWidthScene = m_rect->rect().width();
+ }
+ else {
+ // Fallback, should not happen
+ m_initialTextWidthScene = Rez::guiX(MinTextWidthDocument * 2);
+ }
+ }
+
+ m_lastGoodWidthScene = m_initialTextWidthScene;
+
+ event->accept();
+ }
+ else {
+ m_isResizing = false;
+ QGIView::mousePressEvent(event);
}
}
+void QGIRichAnno::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
+{
+ if (m_isResizing && m_currentResizeHandle != ResizeHandle::NoHandle) {
+ TechDraw::DrawRichAnno* annoFeat = getFeature();
+ if (!annoFeat) {
+ return;
+ }
+
+ if (!m_isDraggingMidResize) { // First actual move during this resize op
+ if (!Gui::Control().activeDialog()) {
+ Gui::Command::openCommand(
+ QObject::tr("Resize Rich Annotation").toStdString().c_str());
+ }
+ m_transactionOpen = true;
+ m_isDraggingMidResize = true;
+ }
+
+ QPointF currentMouseScenePos = event->scenePos();
+ double mouseDeltaSceneX = currentMouseScenePos.x() - m_dragStartMouseScenePos.x();
+
+ // 1. Calculate the raw target width based on mouse movement
+ double targetVisualWidthScene = (m_currentResizeHandle == ResizeHandle::RightHandle)
+ ? m_initialTextWidthScene + mouseDeltaSceneX
+ : m_initialTextWidthScene - mouseDeltaSceneX;
+
+ // Clamp against the geometric minimum
+ double geometricMinWidthScene = Rez::guiX(MinTextWidthDocument);
+ if (targetVisualWidthScene < geometricMinWidthScene) {
+ targetVisualWidthScene = geometricMinWidthScene;
+ }
+
+ // --- 2. Perform Synchronous "What-If" Analysis ---
+ double finalVisualWidthScene = 0;
+ const double originalDocWidth = m_text->document()->textWidth();
+ const double targetWidthLocal = targetVisualWidthScene / m_textScaleFactor;
+ m_text->document()->setTextWidth(targetWidthLocal);
+ QSizeF actualSizeLocal = m_text->document()->documentLayout()->documentSize();
+ m_text->document()->setTextWidth(originalDocWidth);
+
+ constexpr double tolerance = 1e-5;
+ if (actualSizeLocal.width() > targetWidthLocal + tolerance) {
+ finalVisualWidthScene = m_lastGoodWidthScene;
+ }
+ else {
+ finalVisualWidthScene = targetVisualWidthScene;
+ m_lastGoodWidthScene = finalVisualWidthScene;
+ }
+
+ if (annoFeat->OriginCentered.getValue()) {
+ // --- 3. Calculate Final CENTER Position based on Validated Width ---
+ // Calculate the *actual* change in width that resulted from the what-if analysis.
+ const double actualWidthChange = finalVisualWidthScene - m_initialTextWidthScene;
+
+ // The center of the item moves by exactly half of this actual change.
+ double positionDeltaX = 0;
+ if (m_currentResizeHandle == ResizeHandle::RightHandle) {
+ // Width was added to the right side, so center moves right.
+ positionDeltaX = actualWidthChange / 2.0;
+ }
+ else { // LeftHandle
+ // Width was added to the left side, so center moves left.
+ positionDeltaX = -actualWidthChange / 2.0;
+ }
+
+ const double newItemScenePosX = m_initialItemScenePos.x() + positionDeltaX;
+
+ annoFeat->MaxWidth.setValue(Rez::appX(finalVisualWidthScene));
+ annoFeat->X.setValue(Rez::appX(newItemScenePosX));
+
+ updateView(true);
+ }
+ else {
+ // --- 3. Calculate Final Position based on Validated Width ---
+ double newItemScenePosX = 0;
+ if (m_currentResizeHandle == ResizeHandle::RightHandle) {
+ newItemScenePosX = m_initialItemScenePos.x();
+ }
+ else { // LeftHandle
+ newItemScenePosX =
+ m_initialItemScenePos.x() + (m_initialTextWidthScene - finalVisualWidthScene);
+ }
+
+ // --- 4. Commit Final, Validated State to the Feature ---
+ annoFeat->MaxWidth.setValue(Rez::appX(finalVisualWidthScene));
+ annoFeat->X.setValue(Rez::appX(newItemScenePosX));
+ }
+
+ // The property changes will trigger QGIRichAnno::updateView, which handles the visual
+ // update.
+
+ QTimer::singleShot(0, this, [this]() {
+ if (this && scene()) {
+ Q_EMIT positionChanged(scenePos());
+ }
+ });
+
+ event->accept();
+ }
+ else {
+ QGIView::mouseMoveEvent(event);
+ }
+}
+
+void QGIRichAnno::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
+{
+ if (m_isResizing) {
+ if (m_transactionOpen) {
+ // Only commit if actual dragging (and thus property changes) occurred.
+ // m_isDraggingMidResize flag indicates if mouseMoveEvent was processed.
+ if (!Gui::Control().activeDialog()) {
+ Gui::Command::commitCommand();
+ }
+ m_transactionOpen = false;
+ widthChanged();
+ }
+ m_isResizing = false;
+ m_isDraggingMidResize = false;
+ m_currentResizeHandle = ResizeHandle::NoHandle;
+ setCursor(Qt::ArrowCursor); // Reset cursor
+
+ // Ensure focus returns to the text item after the resize handle is released
+ refocusAnnotation();
+
+ Q_EMIT positionChanged(scenePos());
+
+ if (!isUnderMouse()) {
+ QGraphicsSceneHoverEvent leaveEvent(QEvent::GraphicsSceneHoverLeave);
+ hoverLeaveEvent(&leaveEvent); // Manually trigger leave event
+ }
+
+ event->accept();
+ }
+ else {
+ QGIView::mouseReleaseEvent(event);
+ }
+}
+
+void QGIRichAnno::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) {
+ Q_UNUSED(event);
+
+ // If resizing was in progress, cancel it to avoid conflict with dialog
+ if (m_isResizing) {
+ if (m_transactionOpen) {
+ // To avoid partial changes, might need to revert or just abort.
+ // For simplicity, we commit if open. A better way would be to store original values and revert.
+ if (!Gui::Control().activeDialog()) {
+ Gui::Command::commitCommand();
+ }
+ m_transactionOpen = false;
+ }
+ m_isResizing = false;
+ m_isDraggingMidResize = false;
+ m_currentResizeHandle = ResizeHandle::NoHandle;
+ setCursor(Qt::ArrowCursor);
+ }
+
+ auto vp = static_cast(getViewProvider(getViewObject()));
+ if (!vp) {
+ return;
+ }
+ vp->doubleClicked();
+}
+
+void QGIRichAnno::setEditMode(bool enable)
+{
+ m_isEditing = enable;
+ if (enable) {
+ m_text->setTextInteractionFlags(Qt::TextEditorInteraction);
+
+ QTextCursor cursor = m_text->textCursor();
+
+ // Check if the document is empty. If so, create a default format.
+ if (m_text->document()->isEmpty()) {
+ // Document is empty, so we need to create a default style from scratch.
+ // Let's use the default label font from preferences.
+ QFont font = PreferencesGui::labelFontQFont();
+
+ QTextCharFormat defaultFormat;
+ defaultFormat.setFontPointSize(font.pointSizeF());
+ cursor.setCharFormat(defaultFormat);
+ }
+ else {
+ // Document has content. Let's use the format of the first character
+ // as the default for any new text.
+ cursor.setPosition(0);
+ QTextCharFormat formatAtStart = cursor.charFormat();
+
+ // Move the cursor back to its original position (or end of document)
+ cursor.movePosition(QTextCursor::End);
+
+ // Apply the format from the start of the document to the current cursor position.
+ // This sets the "default" format for subsequent typing.
+ cursor.setCharFormat(formatAtStart);
+ }
+
+ // IMPORTANT: Apply the modified cursor back to the text item.
+ m_text->setTextCursor(cursor);
+
+ refocusAnnotation();
+
+ Q_EMIT positionChanged(scenePos());
+ }
+ else {
+ m_text->setTextInteractionFlags(Qt::NoTextInteraction);
+ m_text->clearFocus();
+ clearFocus();
+ }
+ update();
+}
+
+void QGIRichAnno::refocusAnnotation()
+{
+ if (scene()) {
+ if (!scene()->views().isEmpty()) {
+ scene()->views().first()->setFocus();
+ }
+
+ scene()->setFocusItem(m_text, Qt::OtherFocusReason);
+ }
+}
+
+QTextDocument* QGIRichAnno::document() const
+{
+ return m_text->document();
+}
+
+QTextCursor QGIRichAnno::textCursor() const
+{
+ return m_text->textCursor();
+}
+
+void QGIRichAnno::setTextCursor(const QTextCursor& cursor)
+{
+ m_text->setTextCursor(cursor);
+}
+
+void QGIRichAnno::onContentsChanged()
+{
+ // Only process changes when in edit mode to avoid loops during setup
+ if (m_isEditing) {
+ // Update the feature property in real-time
+ getFeature()->AnnoText.setValue(m_text->toHtml().toUtf8());
+ // Emit signal for the task panel
+ Q_EMIT textChanged();
+ drawBorder(); // Make sure view frame is updated.
+ }
+}
+
+QVariant QGIRichAnno::itemChange(GraphicsItemChange change, const QVariant& value)
+{
+ if (change == QGraphicsItem::ItemScenePositionHasChanged
+ && scene()) {
+ Q_EMIT positionChanged(scenePos());
+ }
+ return QGIView::itemChange(change, value);
+}
+
+void QGIRichAnno::updateLayout()
+{
+ TechDraw::DrawRichAnno* annoFeat = getFeature();
+ if (!annoFeat || !m_text || m_textScaleFactor <= 0.0) {
+ return;
+ }
+ prepareGeometryChange();
+
+ double maxWidthDoc = annoFeat->MaxWidth.getValue();
+ if (maxWidthDoc > 0.0) {
+ m_text->setTextWidth(Rez::guiX(maxWidthDoc) / m_textScaleFactor);
+ }
+ else {
+ m_text->setTextWidth(-1.0);
+ }
+
+ update();
+
+ if (scene()) {
+ Q_EMIT positionChanged(scenePos());
+ }
+ drawBorder();
+}
+
#include
diff --git a/src/Mod/TechDraw/Gui/QGIRichAnno.h b/src/Mod/TechDraw/Gui/QGIRichAnno.h
index 7cd8762a5e..c818e0078d 100644
--- a/src/Mod/TechDraw/Gui/QGIRichAnno.h
+++ b/src/Mod/TechDraw/Gui/QGIRichAnno.h
@@ -19,7 +19,6 @@
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
-
#pragma once
#include
@@ -33,10 +32,11 @@
#include "QGIUserTypes.h"
-namespace TechDraw {
+namespace TechDraw
+{
class DrawRichAnno;
class DrawLeaderLine;
-}
+} // namespace TechDraw
namespace TechDrawGui
{
@@ -50,23 +50,33 @@ class QGCustomRect;
//*******************************************************************
-class TechDrawGuiExport QGIRichAnno : public QGIView
+class TechDrawGuiExport QGIRichAnno: public QGIView
{
Q_OBJECT
public:
enum {Type = UserType::QGIRichAnno};
+ enum class ResizeHandle
+ {
+ NoHandle,
+ LeftHandle,
+ RightHandle
+ // Future: TopHandle, BottomHandle, CornerHandles
+ };
+
explicit QGIRichAnno();
~QGIRichAnno() override = default;
- int type() const override { return Type;}
- void paint( QPainter * painter,
- const QStyleOptionGraphicsItem * option,
- QWidget * widget = nullptr ) override;
+ int type() const override
+ {
+ return Type;
+ }
+ void paint(QPainter* painter,
+ const QStyleOptionGraphicsItem* option,
+ QWidget* widget = nullptr) override;
QRectF boundingRect() const override;
- void drawBorder() override;
void updateView(bool update = false) override;
void setTextItem();
@@ -74,26 +84,74 @@ public:
virtual TechDraw::DrawRichAnno* getFeature();
QPen rectPen() const;
- void setExportingPdf(bool b) { m_isExportingPdf = b; }
- bool getExportingPdf() const { return m_isExportingPdf; }
- void setExportingSvg(bool b) { m_isExportingSvg = b; }
- bool getExportingSvg() const { return m_isExportingSvg; }
+ void setExportingPdf(bool b)
+ {
+ m_isExportingPdf = b;
+ }
+ bool getExportingPdf() const
+ {
+ return m_isExportingPdf;
+ }
+ void setExportingSvg(bool b)
+ {
+ m_isExportingSvg = b;
+ }
+ bool getExportingSvg() const
+ {
+ return m_isExportingSvg;
+ }
+
+ void setEditMode(bool enable);
+ QTextDocument* document() const;
+ QTextCursor textCursor() const;
+ void setTextCursor(const QTextCursor& cursor);
+ void updateLayout();
+
+ void refocusAnnotation();
+
+ Q_SIGNALS:
+ void widthChanged();
+ void textChanged();
+ void selectionChanged();
+ void positionChanged(const QPointF& scenePos);
protected:
void draw() override;
+ void hoverMoveEvent(QGraphicsSceneHoverEvent* event) override;
+ void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
+ void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override;
+
void setLineSpacing(int lineSpacing);
QFont prefFont(void);
- void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
+ void mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) override;
- QString convertTextSizes(const QString& inHtml) const;
+ QVariant itemChange(GraphicsItemChange change, const QVariant& value) override;
bool m_isExportingPdf;
bool m_isExportingSvg;
QGCustomText* m_text;
- bool m_hasHover;
QGCustomRect* m_rect;
+ // For resizing
+ ResizeHandle m_currentResizeHandle;
+ bool m_isResizing;
+ bool m_isDraggingMidResize; // True if mouse has moved significantly after press during resize
+ bool m_transactionOpen; // True if a Gui::Command transaction is open
+ QPointF m_dragStartMouseScenePos;
+ QPointF m_initialItemScenePos; // Scene pos of QGIRichAnno item (center)
+ double m_initialTextWidthScene; // Scene units, from MaxWidth property
+
+ static const double HandleInteractionMargin; // Margin for grabbing handles (scene units)
+ static const double MinTextWidthDocument; // Minimum resizable width (document units)
+
+ bool m_isEditing;
+ double m_textScaleFactor;
+ double m_lastGoodWidthScene;
+
+private Q_SLOTS:
+ void onContentsChanged();
};
}
\ No newline at end of file
diff --git a/src/Mod/TechDraw/Gui/QGIView.cpp b/src/Mod/TechDraw/Gui/QGIView.cpp
index 4e2c2b5228..1b48678b7f 100644
--- a/src/Mod/TechDraw/Gui/QGIView.cpp
+++ b/src/Mod/TechDraw/Gui/QGIView.cpp
@@ -696,7 +696,11 @@ void QGIView::layoutDecorations(const QRectF& contentArea,
constexpr double padding{10};
QRectF paddedContentArea = contentArea.adjusted(-padding, -padding, padding, padding);
- double frameWidth = qMax(paddedContentArea.width(), labelRect.width());
+ double frameWidth = paddedContentArea.width();
+ // For standard views, expand frame to fit label. For RichAnno, keep frame tight to text.
+ if (type() != UserType::QGIRichAnno) {
+ frameWidth = qMax(frameWidth, labelRect.width());
+ }
double frameHeight = paddedContentArea.height();
outFrameRect = QRectF(paddedContentArea.center().x() - (frameWidth / 2),
diff --git a/src/Mod/TechDraw/Gui/QGVPage.cpp b/src/Mod/TechDraw/Gui/QGVPage.cpp
index 7188fd7179..2428b8aea7 100644
--- a/src/Mod/TechDraw/Gui/QGVPage.cpp
+++ b/src/Mod/TechDraw/Gui/QGVPage.cpp
@@ -393,6 +393,15 @@ void QGVPage::keyPressEvent(QKeyEvent* event)
toolHandler->keyPressEvent(event);
}
else {
+ if (scene() && scene()->focusItem() != nullptr) {
+ // The event belongs to the focused item. The base QGraphicsView implementation
+ // will handle forwarding it correctly.
+ QGraphicsView::keyPressEvent(event);
+
+ // We MUST return here to prevent the navigation style from also
+ // processing (and likely consuming) the event.
+ return;
+ }
m_navStyle->handleKeyPressEvent(event);
}
if (!event->isAccepted()) {
diff --git a/src/Mod/TechDraw/Gui/TaskRichAnno.cpp b/src/Mod/TechDraw/Gui/TaskRichAnno.cpp
index c3ab0af3f3..aa073e129e 100644
--- a/src/Mod/TechDraw/Gui/TaskRichAnno.cpp
+++ b/src/Mod/TechDraw/Gui/TaskRichAnno.cpp
@@ -20,9 +20,12 @@
* *
***************************************************************************/
-# include
-# include
-
+#include
+#include
+#include
+#include
+#include
+#include
#include
#include
@@ -30,18 +33,21 @@
#include
#include
#include
+#include
#include
#include
#include
#include
#include
-#include "ui_TaskRichAnno.h"
+#include "ui_TaskRichAnno.h" //This will include mrichtextedit.h if the .ui file uses MRichTextEdit
#include "TaskRichAnno.h"
-#include "mrichtextedit.h"
+#include "MDIViewPage.h"
#include "PreferencesGui.h"
#include "QGIView.h"
+#include "QGIRichAnno.h"
#include "QGMText.h"
+#include "QGVPage.h"
#include "QGSPage.h"
#include "Rez.h"
#include "ViewProviderPage.h"
@@ -61,11 +67,14 @@ TaskRichAnno::TaskRichAnno(TechDrawGui::ViewProviderRichAnno* annoVP) :
m_annoFeat(nullptr),
m_qgParent(nullptr),
m_createMode(false),
- m_inProgressLock(false),
+ m_placementMode(false),
+ m_inProgressLock(true), // Lock during setup
m_btnOK(nullptr),
m_btnCancel(nullptr),
- m_textDialog(nullptr),
- m_rte(nullptr)
+ m_qgiAnno(nullptr),
+ m_syncLock(false),
+ m_view(nullptr),
+ m_toolbar(nullptr)
{
//existence of annoVP is guaranteed by caller being ViewProviderRichAnno.setEdit
@@ -74,6 +83,7 @@ TaskRichAnno::TaskRichAnno(TechDrawGui::ViewProviderRichAnno* annoVP) :
m_basePage = m_annoFeat->findParentPage();
if (!m_basePage) {
Base::Console().error("TaskRichAnno - bad parameters (2). Cannot proceed.\n");
+ m_inProgressLock = false;
return;
}
@@ -88,23 +98,25 @@ TaskRichAnno::TaskRichAnno(TechDrawGui::ViewProviderRichAnno* annoVP) :
Gui::Document* activeGui = Gui::Application::Instance->getDocument(m_basePage->getDocument());
Gui::ViewProvider* vp = activeGui->getViewProvider(m_basePage);
m_vpp = static_cast(vp);
+ m_view = m_vpp->getMDIViewPage();
m_qgParent = nullptr;
if (m_baseFeat) {
m_qgParent = m_vpp->getQGSPage()->findQViewForDocObj(m_baseFeat);
}
+ QGVPage* graphicsView = nullptr;
+ graphicsView = m_vpp->getQGVPage();
+ m_toolbar = new MRichTextEdit(graphicsView->viewport());
+
+ Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Edit Annotation"));
+
ui->setupUi(this);
m_title = QObject::tr("Rich Text Editor");
setUiEdit();
- m_attachPoint = Rez::guiX(Base::Vector3d(m_annoFeat->X.getValue(),
- -m_annoFeat->Y.getValue(),
- 0.0));
-
- connect(ui->pbEditor, &QPushButton::clicked,
- this, &TaskRichAnno::onEditorClicked);
+ finishSetup();
}
//ctor for creation
@@ -117,87 +129,285 @@ TaskRichAnno::TaskRichAnno(TechDraw::DrawView* baseFeat,
m_annoFeat(nullptr),
m_qgParent(nullptr),
m_createMode(true),
- m_inProgressLock(false),
+ m_placementMode(true),
+ m_inProgressLock(true), // Lock during setup
m_btnOK(nullptr),
m_btnCancel(nullptr),
- m_textDialog(nullptr),
- m_rte(nullptr)
+ m_qgiAnno(nullptr),
+ m_syncLock(false),
+ m_view(nullptr),
+ m_toolbar(nullptr)
{
//existence of baseFeat and page guaranteed by CmdTechDrawRichTextAnnotation (CommandAnnotate.cpp)
Gui::Document* activeGui = Gui::Application::Instance->getDocument(m_basePage->getDocument());
Gui::ViewProvider* vp = activeGui->getViewProvider(m_basePage);
m_vpp = static_cast(vp);
+ m_view = m_vpp->getMDIViewPage();
m_qgParent = nullptr;
if (m_vpp->getQGSPage()) {
m_qgParent = m_vpp->getQGSPage()->findQViewForDocObj(baseFeat);
}
+ Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Create Annotation"));
+
ui->setupUi(this);
m_title = QObject::tr("Rich Text Creator");
- setUiPrimary();
+ setUiPrimary(); // Sets initial UI values, might trigger signals if connected
- connect(ui->pbEditor, &QPushButton::clicked,
- this, &TaskRichAnno::onEditorClicked);
+ // Don't create the feature or the toolbar yet. Enter placement mode instead.
+ QGVPage* graphicsView = m_vpp->getQGVPage();
+ if (graphicsView) {
+ // Avoid double installation
+ if (m_viewport) {
+ m_viewport->removeEventFilter(this);
+ }
+ m_viewport = graphicsView->viewport();
+ m_viewport->installEventFilter(this);
+ }
+
+ enterPlacementMode();
+
+ m_inProgressLock = false;
+}
+
+TaskRichAnno::~TaskRichAnno()
+{
+ removeViewFilter();
+
+ if (m_toolbar) {
+ m_toolbar->close(); // This will delete
+ m_toolbar = nullptr;
+ }
+}
+
+void TaskRichAnno::finishSetup()
+{
+ m_inProgressLock = true; // Lock during setup
+
+ // --- Step 1: Get pointer to the QGIRichAnno object ---
+ if (!m_annoVP || !m_view) {
+ Base::Console().error(
+ "TaskRichAnno::finishSetup - Critical m_annoVP are missing. Aborting setup.\n");
+ return;
+ }
+ m_qgiAnno = static_cast(m_annoVP->getQView());
+ QGVPage* graphicsView = m_view->getViewProviderPage()->getQGVPage();
+
+ if (!m_qgiAnno || !graphicsView) {
+ Base::Console().error(
+ "TaskRichAnno::finishSetup - Critical m_qgiAnno is missing. Aborting setup.\n");
+ return;
+ }
+
+ m_toolbar->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint);
+ m_toolbar->setAttribute(Qt::WA_DeleteOnClose);
+ m_toolbar->setProperty("floating", true);
+
+ // --- Step 2: Perform the "hide text area" trick ---
+ QTextEdit* textEditChild = m_toolbar->findChild();
+ if (!textEditChild) {
+ delete m_toolbar;
+ m_toolbar = nullptr;
+ m_inProgressLock = false;
+ return;
+ }
+
+ textEditChild->setVisible(false);
+ textEditChild->setMinimumHeight(0);
+ textEditChild->setMaximumHeight(0);
+
+ m_toolbar->setMinimalMode(true);
+ m_toolbar->adjustSize();
+ m_toolbar->setFixedSize(m_toolbar->sizeHint());
+ m_toolbar->show();
+
+ // --- Step 3: Connect signals ---
+ // Get the internal document of the toolbar and link it to the QGIRichAnno
+ textEditChild->setDocument(m_qgiAnno->document());
+
+ // Connect signals to keep things in sync
+ connect(m_qgiAnno,
+ &QGIRichAnno::selectionChanged,
+ this,
+ &TaskRichAnno::onViewSelectionChanged);
+ connect(m_qgiAnno,
+ &QGIRichAnno::positionChanged,
+ this,
+ &TaskRichAnno::onViewPositionChanged);
+
+ // Also connect the width changed signal for resize handles
+ connect(m_qgiAnno, &QGIRichAnno::widthChanged, this, &TaskRichAnno::onViewWidthChanged);
+
+ connect(ui->dsbMaxWidth,
+ qOverload(&Gui::QuantitySpinBox::valueChanged),
+ this,
+ &TaskRichAnno::onMaxWidthChanged);
+ connect(ui->gbFrame, &QGroupBox::toggled, this, &TaskRichAnno::onShowFrameToggled);
+ connect(ui->cpFrameColor, &Gui::ColorButton::changed, this, &TaskRichAnno::onFrameColorChanged);
+ connect(ui->dsbWidth,
+ qOverload(&Gui::QuantitySpinBox::valueChanged),
+ this,
+ &TaskRichAnno::onFrameWidthChanged);
+ connect(ui->cFrameStyle,
+ qOverload(&QComboBox::currentIndexChanged),
+ this,
+ &TaskRichAnno::onFrameStyleChanged);
+
+ // Panning is detected by scroll bar value changes.
+ connect(graphicsView->horizontalScrollBar(),
+ &QScrollBar::valueChanged,
+ this,
+ &TaskRichAnno::onViewTransformed);
+ connect(graphicsView->verticalScrollBar(),
+ &QScrollBar::valueChanged,
+ this,
+ &TaskRichAnno::onViewTransformed);
+
+
+ connect(ui->gbFrame, &QGroupBox::toggled, this, &TaskRichAnno::refocusAnnotation);
+ connect(ui->cpFrameColor, &Gui::ColorButton::changed, this, &TaskRichAnno::refocusAnnotation);
+ connect(ui->cFrameStyle,
+ QOverload::of(&QComboBox::activated),
+ this,
+ &TaskRichAnno::refocusAnnotation);
+ connect(ui->dsbMaxWidth,
+ &Gui::QuantitySpinBox::editingFinished,
+ this,
+ &TaskRichAnno::refocusAnnotation);
+ connect(ui->dsbWidth,
+ &Gui::QuantitySpinBox::editingFinished,
+ this,
+ &TaskRichAnno::refocusAnnotation);
+
+ onViewSelectionChanged(); // Sync initial cursor to hidden editor
+ m_qgiAnno->setEditMode(true);
+
+ if (graphicsView) {
+ // Install the event filter on the viewport, which receives the mouse events.
+ if (m_viewport) {
+ m_viewport->removeEventFilter(this);
+ }
+ m_viewport = graphicsView->viewport();
+ m_viewport->installEventFilter(this);
+ }
+
+ QTimer::singleShot(0, m_qgiAnno, &QGIRichAnno::updateLayout);
+
+ m_inProgressLock = false;
+}
+
+void TaskRichAnno::onViewTransformed()
+{
+ // When the view pans, the item's scene position hasn't changed.
+ // We just need to re-run the position calculation with its current scenePos.
+ if (m_qgiAnno) {
+ onViewPositionChanged(m_qgiAnno->scenePos());
+ }
+}
+
+void TaskRichAnno::onViewSelectionChanged()
+{
+ if (m_syncLock) {
+ return;
+ }
+
+ // When the selection in the view changes, we need to update the
+ // toolbar's internal state so the buttons (Bold, etc.) reflect the selection.
+ if (m_toolbar && m_qgiAnno) {
+ QTextEdit* textEditChild = m_toolbar->findChild();
+ if (textEditChild) {
+ m_syncLock = true;
+ textEditChild->setTextCursor(m_qgiAnno->textCursor());
+ m_syncLock = false;
+ }
+ }
+
+}
+
+void TaskRichAnno::onViewPositionChanged(const QPointF& scenePos)
+{
+ // Make sure you have a local variable for the QGVPage to make the code cleaner
+ QGVPage* graphicsView = nullptr;
+ if (m_view) {
+ graphicsView = m_view->getViewProviderPage()->getQGVPage();
+ }
+
+ if (m_toolbar && graphicsView && m_qgiAnno) {
+ // Get the item's bounding rectangle in Scene coordinates
+ QRectF itemRect = m_qgiAnno->mapToScene(m_qgiAnno->boundingRect()).boundingRect();
+
+ // Calculate the top-center point of the item in Scene coordinates
+ QPointF topCenterScenePos(itemRect.center().x(), itemRect.top());
+
+ // Map from scene using the correct QGVPage object
+ QPoint viewPos = graphicsView->mapFromScene(topCenterScenePos);
+
+ // Map the QGraphicsView point to global screen coordinates
+ QPoint globalPos = graphicsView->mapToGlobal(viewPos);
+
+ // Position the toolbar above this point, centered horizontally
+ int yOffset = 10;
+ QPoint toolbarPos(globalPos.x() - m_toolbar->width() / 2,
+ globalPos.y() - m_toolbar->height() - yOffset);
+
+ m_toolbar->move(toolbarPos);
+
+ // Ensure the toolbar is raised to the top
+ m_toolbar->raise();
+ }
}
void TaskRichAnno::updateTask()
{
// blockUpdate = true;
-
-// blockUpdate = false;
}
void TaskRichAnno::changeEvent(QEvent *event)
{
if (event->type() == QEvent::LanguageChange) {
ui->retranslateUi(this);
+ // Update titles if they are translatable and set directly
+ if (m_createMode) {
+ m_title = QObject::tr("Rich text creator");
+ } else {
+ m_title = QObject::tr("Rich text editor");
+ }
+ if (m_annoFeat || m_createMode) { // Only set window title if UI is relevant
+ setWindowTitle(m_title);
+ }
}
+ QWidget::changeEvent(event);
}
void TaskRichAnno::setUiPrimary()
{
-// Base::Console().message("TRA::setUiPrimary()\n");
- enableVPUi(false);
setWindowTitle(m_title);
if (m_baseFeat) {
std::string baseName = m_baseFeat->getNameInDocument();
ui->leBaseView->setText(QString::fromStdString(baseName));
}
+ ui->dsbMaxWidth->setUnit(Base::Unit::Length);
+ ui->dsbMaxWidth->setValue(-1.0);
+
ui->dsbWidth->setUnit(Base::Unit::Length);
ui->dsbWidth->setMinimum(0);
ui->dsbWidth->setValue(prefWeight());
+
ui->cpFrameColor->setColor(prefLineColor().asValue());
- // set a default font size, use for this the preferences setting
- MRichTextEdit mre;
- ui->teAnnoText->setFontPointSize(mre.getDefFontSizeNum());
- // set a placeholder text to inform the user
- ui->teAnnoText->setPlaceholderText(tr("Input the annotation text directly or start the rich text editor"));
+ ui->gbFrame->setChecked(false);
}
void TaskRichAnno::enableTextUi(bool enable)
{
- ui->pbEditor->setEnabled(enable);
- ui->teAnnoText->setEnabled(enable);
-}
-
-//switch widgets related to ViewProvider on/off
-//there is no ViewProvider until some time after feature is created.
-void TaskRichAnno::enableVPUi(bool enable)
-{
- Q_UNUSED(enable);
-// ui->cpLineColor->setEnabled(b);
-// ui->dsbWeight->setEnabled(b);
-// ui->cboxStyle->setEnabled(b);
+ m_toolbar->setEnabled(enable);
}
void TaskRichAnno::setUiEdit()
{
-// Base::Console().message("TRA::setUiEdit());
- enableVPUi(true);
setWindowTitle(m_title);
enableTextUi(true);
@@ -208,9 +418,9 @@ void TaskRichAnno::setUiEdit()
baseName = docObj->getNameInDocument();
}
ui->leBaseView->setText(QString::fromStdString(baseName));
- ui->teAnnoText->setHtml(QString::fromUtf8(m_annoFeat->AnnoText.getValue()));
+ m_toolbar->setText(QString::fromUtf8(m_annoFeat->AnnoText.getValue()));
ui->dsbMaxWidth->setValue(m_annoFeat->MaxWidth.getValue());
- ui->cbShowFrame->setChecked(m_annoFeat->ShowFrame.getValue());
+ ui->gbFrame->setChecked(m_annoFeat->ShowFrame.getValue());
}
if (m_annoVP) {
@@ -220,46 +430,73 @@ void TaskRichAnno::setUiEdit()
}
}
-void TaskRichAnno::onEditorClicked(bool clicked)
+
+void TaskRichAnno::onMaxWidthChanged(double value)
{
-// Base::Console().message("TL::onEditorClicked(%d)\n", b);
- Q_UNUSED(clicked);
- m_textDialog = new QDialog(nullptr);
- QString leadText = ui->teAnnoText->toHtml();
- QString plainText = ui->teAnnoText->toPlainText();
- if (plainText.isEmpty()) {
- m_rte = new MRichTextEdit(m_textDialog);
- } else {
- m_rte = new MRichTextEdit(m_textDialog, leadText);
+ createAnnoIfNotAlready();
+
+ if (m_inProgressLock || !m_annoFeat) return;
+ m_annoFeat->MaxWidth.setValue(value);
+ m_annoFeat->requestPaint();
+}
+
+void TaskRichAnno::onViewWidthChanged()
+{
+ ui->dsbMaxWidth->blockSignals(true);
+ ui->dsbMaxWidth->setValue(m_annoFeat->MaxWidth.getValue());
+ ui->dsbMaxWidth->blockSignals(false);
+}
+
+void TaskRichAnno::onShowFrameToggled(bool checked)
+{
+ createAnnoIfNotAlready();
+
+ if (m_inProgressLock || !m_annoFeat) return;
+ m_annoFeat->ShowFrame.setValue(checked);
+ // Update VP editable status based on ShowFrame
+ if (m_annoVP) {
+ bool editable = checked;
+ m_annoVP->LineWidth.setStatus(App::Property::ReadOnly, !editable);
+ m_annoVP->LineStyle.setStatus(App::Property::ReadOnly, !editable);
+ m_annoVP->LineColor.setStatus(App::Property::ReadOnly, !editable);
}
- QGridLayout* gl = new QGridLayout(m_textDialog);
- gl->addWidget(m_rte, 0,0, 1,1);
- m_textDialog->setWindowTitle(QObject::tr("Rich text editor"));
- m_textDialog->setMinimumWidth (400);
- m_textDialog->setMinimumHeight(400);
-
- connect(m_rte, &MRichTextEdit::saveText,
- this, &TaskRichAnno::onSaveAndExit);
- connect(m_rte, &MRichTextEdit::editorFinished,
- this, &TaskRichAnno::onEditorExit);
-
- m_textDialog->show();
+ m_annoFeat->requestPaint();
}
-void TaskRichAnno::onSaveAndExit(QString qs)
+void TaskRichAnno::onFrameColorChanged()
{
- ui->teAnnoText->setHtml(qs);
- //dialog clean up should be handled by accept() call in dialog
- m_textDialog->accept();
- m_textDialog = nullptr;
- m_rte = nullptr;
+ createAnnoIfNotAlready();
+
+ if (m_inProgressLock || !m_annoVP) return;
+ Base::Color ac;
+ ac.setValue(ui->cpFrameColor->color());
+ m_annoVP->LineColor.setValue(ac);
+ if (m_annoFeat) m_annoFeat->requestPaint();
}
-void TaskRichAnno::onEditorExit()
+void TaskRichAnno::onFrameWidthChanged(double value)
{
- m_textDialog->reject();
- m_textDialog = nullptr;
- m_rte = nullptr;
+ createAnnoIfNotAlready();
+
+ if (m_inProgressLock || !m_annoVP) return;
+ m_annoVP->LineWidth.setValue(value);
+ if (m_annoFeat) m_annoFeat->requestPaint();
+}
+
+void TaskRichAnno::onFrameStyleChanged(int index)
+{
+ createAnnoIfNotAlready();
+
+ if (m_inProgressLock || !m_annoVP) return;
+ m_annoVP->LineStyle.setValue(index);
+ if (m_annoFeat) m_annoFeat->requestPaint();
+}
+
+void TaskRichAnno::createAnnoIfNotAlready()
+{
+ if (m_createMode && m_placementMode) {
+ createAndSetupAnnotation(nullptr);
+ }
}
double TaskRichAnno::prefWeight() const
@@ -272,9 +509,152 @@ Base::Color TaskRichAnno::prefLineColor()
return PreferencesGui::leaderColor();
}
+void TaskRichAnno::refocusAnnotation()
+{
+ // Use a zero-delay timer to schedule the focus change.
+ // This allows the current widget interaction (e.g., the checkbox toggling)
+ // to complete fully before we shift focus.
+ QTimer::singleShot(0, [this]() {
+ if (m_qgiAnno) {
+ m_qgiAnno->refocusAnnotation();
+ }
+ });
+}
+
+void TaskRichAnno::focusOutEvent(QFocusEvent* event)
+{
+ // Let the base class do its thing first
+ QWidget::focusOutEvent(event);
+
+ // If the focus is leaving our task panel for something else,
+ // ensure our annotation is the active element.
+ refocusAnnotation();
+}
+
+bool TaskRichAnno::eventFilter(QObject* watched, QEvent* event)
+{
+ if (!m_view || !m_view->getViewProviderPage() || !m_viewport) {
+ return QWidget::eventFilter(watched, event);
+ }
+
+ QGVPage* graphicsView = m_view->getViewProviderPage()->getQGVPage();
+ if (!graphicsView) {
+ return QWidget::eventFilter(watched, event);
+ }
+
+ if (watched == m_viewport) {
+ if (event->type() == QEvent::Enter) {
+ if (!m_placementMode && m_qgiAnno) {
+ refocusAnnotation();
+ }
+ }
+
+ if (event->type() == QEvent::MouseButtonPress) {
+ if (m_createMode && m_placementMode) {
+ auto mouseEvent = static_cast(event);
+ QPointF scenePos = graphicsView->mapToScene(mouseEvent->pos());
+ createAndSetupAnnotation(&scenePos);
+ return QWidget::eventFilter(watched, event);
+ }
+
+ // Cast the event to get the mouse position
+ auto mouseEvent = static_cast(event);
+ QGraphicsItem* item = graphicsView->itemAt(mouseEvent->pos());
+
+ // Walk up the parent chain to see if the click was on our annotation or one of its
+ // children.
+ QGIRichAnno* clickedAnno = nullptr;
+ while (item) {
+ clickedAnno = dynamic_cast(item);
+ if (clickedAnno) {
+ break; // Found an annotation
+ }
+ item = item->parentItem(); // Check the parent
+ }
+
+ // If we didn't find our specific annotation, the click was "outside".
+ if (clickedAnno != m_qgiAnno) {
+ // Simulate clicking the "OK" button to accept the changes.
+ if (m_btnOK && m_btnOK->isEnabled()) {
+ m_btnOK->click();
+ return true; // We've handled this event, so stop further processing.
+ }
+ }
+ else if (!m_placementMode && m_qgiAnno) {
+ refocusAnnotation();
+ }
+ }
+ }
+
+ // For all other events, pass them on to the default handler.
+ return QWidget::eventFilter(watched, event);
+}
+
+void TaskRichAnno::removeViewFilter()
+{
+ if (m_viewport) {
+ m_viewport->removeEventFilter(this);
+ m_viewport = nullptr;
+ }
+}
//******************************************************************************
-void TaskRichAnno::createAnnoFeature()
+void TaskRichAnno::enterPlacementMode()
+{
+ if (m_view) {
+ if (auto* gv = m_view->getViewProviderPage()->getQGVPage()) {
+ gv->viewport()->setCursor(Qt::CrossCursor);
+ }
+ }
+ // Disable UI elements that require an annotation to exist
+ ui->gbFrame->setEnabled(false);
+ ui->dsbMaxWidth->setEnabled(false);
+ setFocus(); // Set focus to the panel to capture key presses
+}
+
+void TaskRichAnno::createAndSetupAnnotation(const QPointF* scenePos)
+{
+ if (!m_placementMode) {
+ return; // Already created
+ }
+ m_inProgressLock = true;
+ m_placementMode = false;
+
+ // Restore cursor
+ if (m_view) {
+ if (auto* gv = m_view->getViewProviderPage()->getQGVPage()) {
+ gv->viewport()->setCursor(Qt::ArrowCursor);
+ }
+ }
+
+ // Now that the feature exists, create the toolbar and finish setup
+ QGVPage* graphicsView = m_vpp->getQGVPage();
+ m_toolbar = new MRichTextEdit(graphicsView->viewport());
+
+ createAnnoFeature(scenePos); // Create the feature at the specified position
+
+ if (!m_annoFeat) { // Safety check if creation failed
+ Base::Console().error("TaskRichAnno - Failed to create annotation feature.\n");
+ m_inProgressLock = false;
+ reject(); // Abort the task
+ return;
+ }
+
+ finishSetup(); // This will connect all signals and show the toolbar
+
+ // Re-enable the UI
+ ui->gbFrame->setEnabled(true);
+ ui->dsbMaxWidth->setEnabled(true);
+
+ // Select the new annotation object so that the frame is shown
+ Gui::Selection().addSelection(m_annoFeat->getDocument()->getName(), m_annoFeat->getNameInDocument());
+
+ refocusAnnotation(); // Give focus to the new annotation
+
+ m_inProgressLock = false;
+}
+
+void TaskRichAnno::createAnnoFeature(const QPointF* scenePos)
{
// Base::Console().message("TRA::createAnnoFeature()");
const std::string objectName{QT_TR_NOOP("RichTextAnnotation")};
@@ -284,7 +664,6 @@ void TaskRichAnno::createAnnoFeature()
std::string PageName = m_basePage->getNameInDocument();
- Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Create Anno"));
Command::doCommand(Command::Doc, "App.activeDocument().addObject('%s', '%s')",
annoType.c_str(), annoName.c_str());
Command::doCommand(Command::Doc, "App.activeDocument().%s.addView(App.activeDocument().%s)",
@@ -296,12 +675,18 @@ void TaskRichAnno::createAnnoFeature()
}
App::DocumentObject* obj = m_basePage->getDocument()->getObject(annoName.c_str());
if (!obj) {
+ Gui::Command::abortCommand();
throw Base::RuntimeError("TaskRichAnno - new RichAnno object not found");
}
if (obj->isDerivedFrom()) {
m_annoFeat = static_cast(obj);
- commonFeatureUpdate();
- if (m_baseFeat) {
+ commonFeatureUpdate(); // Set text, MaxWidth, ShowFrame from UI
+ if (scenePos) {
+ // New: Use the clicked position
+ m_annoFeat->X.setValue(Rez::appX(scenePos->x()));
+ m_annoFeat->Y.setValue(-Rez::appX(scenePos->y()));
+ }
+ else if (m_baseFeat) {
QPointF qTemp = calcTextStartPos(m_annoFeat->getScale());
Base::Vector3d vTemp(qTemp.x(), qTemp.y());
m_annoFeat->X.setValue(Rez::appX(vTemp.x));
@@ -315,22 +700,25 @@ void TaskRichAnno::createAnnoFeature()
if (m_annoFeat) {
Gui::ViewProvider* vp = QGIView::getViewProvider(m_annoFeat);
- auto annoVP = freecad_cast(vp);
- if (annoVP) {
+ m_annoVP = freecad_cast(vp); // Store m_annoVP
+ if (m_annoVP) {
Base::Color ac;
ac.setValue(ui->cpFrameColor->color());
- annoVP->LineColor.setValue(ac);
- annoVP->LineWidth.setValue(ui->dsbWidth->rawValue());
- annoVP->LineStyle.setValue(ui->cFrameStyle->currentIndex());
+ m_annoVP->LineColor.setValue(ac);
+ m_annoVP->LineWidth.setValue(ui->dsbWidth->rawValue());
+ m_annoVP->LineStyle.setValue(ui->cFrameStyle->currentIndex());
+
+ // Set initial VP property readonly state based on ShowFrame
+ bool editable = m_annoFeat->ShowFrame.getValue();
+ m_annoVP->LineWidth.setStatus(App::Property::ReadOnly, !editable);
+ m_annoVP->LineStyle.setStatus(App::Property::ReadOnly, !editable);
+ m_annoVP->LineColor.setStatus(App::Property::ReadOnly, !editable);
}
}
std::string translatedObjectName{tr(objectName.c_str()).toStdString()};
obj->Label.setValue(translatedObjectName + generatedSuffix);
- Gui::Command::commitCommand();
- Gui::Command::updateActive();
-
//trigger claimChildren in tree
if (m_baseFeat) {
m_baseFeat->touch();
@@ -343,55 +731,14 @@ void TaskRichAnno::createAnnoFeature()
}
}
-void TaskRichAnno::updateAnnoFeature()
-{
-// Base::Console().message("TRA::updateAnnoFeature()\n");
- Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Edit Anno"));
- commonFeatureUpdate();
- Base::Color ac;
- ac.setValue(ui->cpFrameColor->color());
- m_annoVP->LineColor.setValue(ac);
- m_annoVP->LineWidth.setValue(ui->dsbWidth->rawValue());
- m_annoVP->LineStyle.setValue(ui->cFrameStyle->currentIndex());
-
- Gui::Command::commitCommand();
- Gui::Command::updateActive();
-}
-
void TaskRichAnno::commonFeatureUpdate()
{
// Base::Console().message("TRA::commonFeatureUpdate()\n");
- m_annoFeat->setPosition(Rez::appX(m_attachPoint.x), Rez::appX(- m_attachPoint.y), true);
- m_annoFeat->AnnoText.setValue(ui->teAnnoText->toHtml().toUtf8());
+ if (!m_annoFeat) return;
+
+ m_annoFeat->AnnoText.setValue(m_toolbar->toHtml().toUtf8());
m_annoFeat->MaxWidth.setValue(ui->dsbMaxWidth->value().getValue());
- m_annoFeat->ShowFrame.setValue(ui->cbShowFrame->isChecked());
-}
-
-void TaskRichAnno::removeFeature()
-{
-// Base::Console().message("TRA::removeFeature()\n");
- if (!m_annoFeat)
- return;
-
- if (m_createMode) {
- try {
- // this doesn't remove the QGMText item??
- std::string PageName = m_basePage->getNameInDocument();
- Gui::Command::doCommand(Gui::Command::Gui, "App.activeDocument().%s.removeView(App.activeDocument().%s)",
- PageName.c_str(), m_annoFeat->getNameInDocument());
- Gui::Command::doCommand(Gui::Command::Gui, "App.activeDocument().removeObject('%s')",
- m_annoFeat->getNameInDocument());
- }
- catch (...) {
- Base::Console().warning("TRA::removeFeature - failed to delete feature\n");
- return;
- }
- } else {
- if (Gui::Command::hasPendingCommand()) {
- std::vector undos = Gui::Application::Instance->activeDocument()->getUndoVector();
- Gui::Application::Instance->activeDocument()->undo(1);
- }
- }
+ m_annoFeat->ShowFrame.setValue(ui->gbFrame->isChecked());
}
//we don't know the bounding rect of the text, so we have to calculate a reasonable
@@ -400,51 +747,63 @@ QPointF TaskRichAnno::calcTextStartPos(double scale)
{
Q_UNUSED(scale)
// Base::Console().message("TRA::calcTextStartPos(%.3f)\n", scale);
- double textWidth = 100.0;
- double textHeight = 20.0;
- double horizGap(20.0);
- double tPosX(0.0);
- double tPosY(0.0);
+ double textWidth = 100.0; // Default guess for text width in document units
+ double textHeight = 20.0; // Default guess for text height
+ double horizGap(Rez::appX(5.0)); // 5mm gap from leader end point in document units
- double width = m_annoFeat->MaxWidth.getValue();
- if (width > 0 ) {
- textWidth = width;
+ double annoMaxWidth = ui->dsbMaxWidth->value().getValue(); // MaxWidth from UI
+ if (annoMaxWidth > 0 ) {
+ textWidth = annoMaxWidth; // Use user-defined MaxWidth if available
}
+ // Note: textHeight is harder to guess accurately without rendering.
- std::vector points;
if (m_baseFeat) {
if (m_baseFeat->isDerivedFrom()) {
TechDraw::DrawLeaderLine* dll = static_cast(m_baseFeat);
- points = dll->WayPoints.getValues();
- } else {
-// Base::Console().message("TRA::calcTextPos - m_baseFeat is not Leader\n");
- return QPointF(0.0, 0.0);
- }
- } else {
-// Base::Console().message("TRA::calcStartPos - no m_baseFeat\n");
- if (m_basePage) {
- double w = Rez::guiX(m_basePage->getPageWidth() / 2.0);
- double h = Rez::guiX(m_basePage->getPageHeight() / 2.0);
- return QPointF(w, h);
- } else {
- Base::Console().message("TRA::calcStartPos - no m_basePage\n"); //shouldn't happen. caught elsewhere
+ const auto& wayPoints = dll->WayPoints.getValues();
+ if (!wayPoints.empty()) {
+ Base::Vector3d leaderEndDoc = wayPoints.back(); // Last point of leader in document units
+ Base::Vector3d leaderPrevDoc = (wayPoints.size() > 1) ? wayPoints[wayPoints.size()-2] : leaderEndDoc;
+
+ // Position relative to leader end point (these are document units)
+ double tPosX_doc, tPosY_doc;
+
+ // Heuristic: place annotation to the right or left of the leader's last segment
+ // Anchor point of RichAnno is its center. We want text block (top-left) relative to leader.
+ // This calculation is for the *center* of the RichAnno.
+ if (leaderEndDoc.x < leaderPrevDoc.x) { // Leader pointing left-ish
+ tPosX_doc = leaderEndDoc.x - horizGap - (textWidth / 2.0);
+ } else { // Leader pointing right-ish or vertical
+ tPosX_doc = leaderEndDoc.x + horizGap + (textWidth / 2.0);
+ }
+ // Vertically, align center of text block slightly below leader end for now
+ tPosY_doc = leaderEndDoc.y - (textHeight / 2.0) ;
+
+ return QPointF(Rez::guiX(tPosX_doc), Rez::guiX(tPosY_doc)); // Convert to GUI coords for return
+ }
}
+ // If baseFeat is not a leader or has no points, fall through to page center or view center.
+ // For a generic DrawView, position near its center.
+ double baseX_doc = m_baseFeat->X.getValue();
+ double baseY_doc = m_baseFeat->Y.getValue();
+
+ // Position annotation center to the right of baseX,
+ // and vertically such that top of annotation is near baseY.
+ double tPosX_doc = baseX_doc + (textWidth / 2.0) + horizGap;
+ // RichAnno Y is its center. baseY_doc is top of view.
+ // So, center of anno = baseY_doc (top of view) + half of anno's estimated height.
+ double tPosY_doc = baseY_doc + (textHeight / 2.0);
+ return QPointF(Rez::guiX(tPosX_doc), Rez::guiX(tPosY_doc)); // Convert to GUI coords for
}
- if (!points.empty()) {
- QPointF lastPoint(points.back().x, points.back().y);
- QPointF firstPoint(points.front().x, points.front().y);
- QPointF lastOffset = lastPoint - firstPoint;
-
- if (lastPoint.x() < firstPoint.x()) { //last is left of first
- tPosX = lastOffset.x() - horizGap - textWidth; //left of last
- tPosY = lastOffset.y() - textHeight;
- } else { //last is right of first
- tPosX = lastOffset.x() + horizGap; //right of last
- tPosY = lastOffset.y() - textHeight;
- }
+ // Default to page center if no baseFeat
+ if (m_basePage) {
+ double w_doc = m_basePage->getPageWidth() / 2.0;
+ double h_doc = m_basePage->getPageHeight() / 2.0;
+ return QPointF(Rez::guiX(w_doc), Rez::guiX(h_doc));
}
- return QPointF(tPosX, -tPosY);
+
+ return QPointF(0.0, 0.0); // Absolute fallback
}
void TaskRichAnno::saveButtons(QPushButton* btnOK,
@@ -456,61 +815,52 @@ void TaskRichAnno::saveButtons(QPushButton* btnOK,
void TaskRichAnno::enableTaskButtons(bool enable)
{
- m_btnOK->setEnabled(enable);
- m_btnCancel->setEnabled(enable);
+ if (m_btnOK) m_btnOK->setEnabled(enable);
+ if (m_btnCancel) m_btnCancel->setEnabled(enable);
}
//******************************************************************************
bool TaskRichAnno::accept()
{
-// Base::Console().message("TRA::accept()\n");
- if (m_inProgressLock) {
-// Base::Console().message("TRA::accept - edit in progress!!\n");
- //TODO: kill MRTE dialog?
- return true;
- }
-
- Gui::Document* doc = Gui::Application::Instance->getDocument(m_basePage->getDocument());
- if (!doc)
+ if (m_inProgressLock || !m_annoFeat) { // Should not happen if UI is responsive
return false;
-
- if (getCreateMode()) {
- createAnnoFeature();
- } else {
- updateAnnoFeature();
}
- m_annoFeat->requestPaint();
+ removeViewFilter();
+ if (m_qgiAnno) {
+ m_qgiAnno->setEditMode(false);
+ }
+
+ Gui::Command::commitCommand();
Gui::Command::doCommand(Gui::Command::Gui, "Gui.ActiveDocument.resetEdit()");
+ m_annoFeat->getDocument()->recompute();
+
return true;
}
bool TaskRichAnno::reject()
{
-// Base::Console().message("TRA::reject()\n");
if (m_inProgressLock) {
-// Base::Console().message("TRA::reject - edit in progress!!\n");
return false;
}
- if (m_basePage) {
- Gui::Document* doc = Gui::Application::Instance->getDocument(m_basePage->getDocument());
- if (!doc) {
- return false;
- }
- if (getCreateMode() && m_annoFeat) {
- removeFeature();
- }
+ if (m_qgiAnno) {
+ m_qgiAnno->setEditMode(false);
}
+
+ removeViewFilter();
- //make sure any dangling objects are cleaned up
- Gui::Command::doCommand(Gui::Command::Gui, "App.activeDocument().recompute()");
+ Gui::Command::abortCommand();
Gui::Command::doCommand(Gui::Command::Gui, "Gui.ActiveDocument.resetEdit()");
- return false;
+ if (!m_createMode) { // Feature gone and m_annoFeat dangling if we are creating!
+ m_annoFeat->getDocument()->recompute();
+ }
+
+ return true;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -519,31 +869,32 @@ TaskDlgRichAnno::TaskDlgRichAnno(TechDraw::DrawView* baseFeat,
: TaskDialog()
{
widget = new TaskRichAnno(baseFeat, page);
- taskbox = new Gui::TaskView::TaskBox(Gui::BitmapFactory().pixmap("actions/TechDraw_RichTextAnnotation"),
+ taskbox = new Gui::TaskView::TaskBox(Gui::BitmapFactory().pixmap("actions/TechDraw_Annotation"),
widget->windowTitle(), true, nullptr);
taskbox->groupLayout()->addWidget(widget);
Content.push_back(taskbox);
+ setAutoCloseOnTransactionChange(true);
+ if (page) {
+ setDocumentName(page->getDocument()->getFullName());
+ }
}
TaskDlgRichAnno::TaskDlgRichAnno(TechDrawGui::ViewProviderRichAnno* annoVP)
: TaskDialog()
{
widget = new TaskRichAnno(annoVP);
- taskbox = new Gui::TaskView::TaskBox(Gui::BitmapFactory().pixmap("actions/TechDraw_RichTextAnnotation"),
+ taskbox = new Gui::TaskView::TaskBox(Gui::BitmapFactory().pixmap("actions/TechDraw_Annotation"),
widget->windowTitle(), true, nullptr);
taskbox->groupLayout()->addWidget(widget);
Content.push_back(taskbox);
+ setAutoCloseOnTransactionChange(true);
+ setDocumentName(annoVP->getDocument()->getDocument()->getFullName());
}
TaskDlgRichAnno::~TaskDlgRichAnno()
{
}
-void TaskDlgRichAnno::update()
-{
-// widget->updateTask();
-}
-
void TaskDlgRichAnno::modifyStandardButtons(QDialogButtonBox* box)
{
QPushButton* btnOK = box->button(QDialogButtonBox::Ok);
@@ -552,24 +903,20 @@ void TaskDlgRichAnno::modifyStandardButtons(QDialogButtonBox* box)
}
//==== calls from the TaskView ===============================================================
-void TaskDlgRichAnno::open()
-{
-}
-
-void TaskDlgRichAnno::clicked(int)
-{
-}
bool TaskDlgRichAnno::accept()
{
- widget->accept();
- return true;
+ return widget->accept(); // Delegate to the widget's accept logic
}
bool TaskDlgRichAnno::reject()
{
- widget->reject();
- return true;
+ return widget->reject(); // Delegate to the widget's reject logic
+}
+
+void TaskDlgRichAnno::autoClosedOnTransactionChange()
+{
+ reject();
}
#include
diff --git a/src/Mod/TechDraw/Gui/TaskRichAnno.h b/src/Mod/TechDraw/Gui/TaskRichAnno.h
index ff1eadf0f7..c80544fae6 100644
--- a/src/Mod/TechDraw/Gui/TaskRichAnno.h
+++ b/src/Mod/TechDraw/Gui/TaskRichAnno.h
@@ -26,9 +26,8 @@
#include
#include
#include
-
-
-class MRichTextEdit;
+#include "mrichtextedit.h"
+#include "MDIViewPage.h"
class Ui_TaskRichAnno;
@@ -43,9 +42,9 @@ namespace TechDrawGui
{
class QGIView;
class QGIPrimPath;
+class QGIRichAnno;
class MDIViewPage;
class QGMText;
-class QGIRichAnno;
class ViewProviderRichAnno;
class ViewProviderPage;
@@ -54,45 +53,60 @@ class TaskRichAnno : public QWidget
Q_OBJECT
public:
- TaskRichAnno(TechDraw::DrawView* baseFeat,
- TechDraw::DrawPage* page);
+ TaskRichAnno(TechDraw::DrawView* baseFeat, TechDraw::DrawPage* page);
explicit TaskRichAnno(TechDrawGui::ViewProviderRichAnno* annoVP);
- ~TaskRichAnno() override = default;
+ ~TaskRichAnno() override;
+
+ void finishSetup();
virtual bool accept();
virtual bool reject();
virtual void setCreateMode(bool mode) { m_createMode = mode; }
virtual bool getCreateMode() { return m_createMode; }
void updateTask();
- void saveButtons(QPushButton* btnOK,
- QPushButton* btnCancel);
+ void saveButtons(QPushButton* btnOK, QPushButton* btnCancel);
void enableTaskButtons(bool enable);
-public Q_SLOTS:
- void onEditorClicked(bool clicked);
+ ViewProviderRichAnno* getAnnoVP() const
+ {
+ return m_annoVP;
+ }
protected:
- void changeEvent(QEvent *event) override;
+ void changeEvent(QEvent* event) override;
+ void focusOutEvent(QFocusEvent* event) override;
+ bool eventFilter(QObject* watched, QEvent* event) override;
- void createAnnoFeature();
- void updateAnnoFeature();
+ void createAnnoFeature(const QPointF* scenePos = nullptr);
void commonFeatureUpdate();
- void removeFeature();
QPointF calcTextStartPos(double scale);
void setUiPrimary();
void setUiEdit();
void enableTextUi(bool enable);
- void enableVPUi(bool enable);
double prefWeight() const;
Base::Color prefLineColor();
protected Q_SLOTS:
- void onSaveAndExit(QString);
- void onEditorExit();
+ void onMaxWidthChanged(double value);
+ void onViewWidthChanged();
+ void onShowFrameToggled(bool checked);
+ void onFrameColorChanged();
+ void onFrameWidthChanged(double value);
+ void onFrameStyleChanged(int index);
+ void onViewTransformed();
+ void refocusAnnotation();
+
+ void onViewSelectionChanged();
+ void onViewPositionChanged(const QPointF& scenePos);
private:
+ void removeViewFilter();
+ void enterPlacementMode();
+ void createAndSetupAnnotation(const QPointF* scenePos = nullptr);
+ void createAnnoIfNotAlready();
+
std::unique_ptr ui;
ViewProviderPage* m_vpp;
@@ -103,18 +117,22 @@ private:
QGIView* m_qgParent;
std::string m_qgParentName;
- Base::Vector3d m_attachPoint;
-
bool m_createMode;
+ bool m_placementMode;
bool m_inProgressLock;
QPushButton* m_btnOK;
QPushButton* m_btnCancel;
- QDialog* m_textDialog;
- MRichTextEdit* m_rte;
QString m_title;
+
+ QGIRichAnno* m_qgiAnno;
+ bool m_syncLock;
+ MDIViewPage* m_view;
+
+ QPointer m_toolbar {nullptr};
+ QPointer m_viewport {nullptr};
};
class TaskDlgRichAnno : public Gui::TaskView::TaskDialog
@@ -122,28 +140,28 @@ class TaskDlgRichAnno : public Gui::TaskView::TaskDialog
Q_OBJECT
public:
- TaskDlgRichAnno(TechDraw::DrawView* baseFeat,
- TechDraw::DrawPage* page);
+ TaskDlgRichAnno(TechDraw::DrawView* baseFeat, TechDraw::DrawPage* page);
explicit TaskDlgRichAnno(TechDrawGui::ViewProviderRichAnno* annoVP);
~TaskDlgRichAnno() override;
public:
- /// is called the TaskView when the dialog is opened
- void open() override;
- /// is called by the framework if an button is clicked which has no accept or reject role
- void clicked(int) override;
/// is called by the framework if the dialog is accepted (Ok)
bool accept() override;
/// is called by the framework if the dialog is rejected (Cancel)
bool reject() override;
+ void autoClosedOnTransactionChange() override;
/// is called by the framework if the user presses the help button
bool isAllowedAlterDocument() const override
- { return false; }
- void update();
+ {
+ return false;
+ }
void modifyStandardButtons(QDialogButtonBox* box) override;
-protected:
+ bool isFor(ViewProviderRichAnno* vp) const
+ {
+ return (widget->getAnnoVP() == vp);
+ }
private:
TaskRichAnno * widget;
diff --git a/src/Mod/TechDraw/Gui/TaskRichAnno.ui b/src/Mod/TechDraw/Gui/TaskRichAnno.ui
index ff1233af22..6002e590de 100644
--- a/src/Mod/TechDraw/Gui/TaskRichAnno.ui
+++ b/src/Mod/TechDraw/Gui/TaskRichAnno.ui
@@ -6,22 +6,10 @@
0
0
- 409
- 419
+ 150
+ 80
-
-
- 0
- 0
-
-
-
-
- 250
- 0
-
-
Rich Text Annotation Block
@@ -31,209 +19,157 @@
-
-
-
-
- 0
- 0
-
+
+
+ Base feature
-
- QFrame::Box
+
+
+ -
+
+
+ false
-
- QFrame::Raised
+
+
+ -
+
+
+ Width
-
+
+
+ -
+
+
+ Maximal width of the annotation block, if -1 then automatic width
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ -1.000000000000000
+
+
+ -1.000000000000000
+
+
+
+ -
+
+
+ Frame
+
+
+ true
+
+
+ false
+
+
+
+ 6
+
+
+ 6
+
+
+ 6
+
+
+ 4
+
-
-
-
-
-
-
- Base feature
-
-
-
- -
-
-
- false
-
-
-
- -
-
-
- Max width
-
-
-
- -
-
-
- Maximal width, if -1 then automatic width
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
- -1.000000000000000
-
-
- -1.000000000000000
-
-
-
-
+
+
+ Thickness
+
+
+
+ -
+
+
+ Line width
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ 0.100000000000000
+
+
+ 0.500000000000000
+
+
-
-
-
- Qt::Horizontal
+
+
+ Style
+ -
+
+
+ Line style
+
+
+ 1
+
+
-
+
+ NoLine
+
+
+ -
+
+ Continuous
+
+
+ -
+
+ Dash
+
+
+ -
+
+ Dot
+
+
+ -
+
+ DashDot
+
+
+ -
+
+ DashDotDot
+
+
+
+
-
-
-
- true
-
+
- Start Rich Text Editor
+ Color
- -
-
-
-
- 0
- 0
-
+
-
+
+
+ Line color
+
+
+
+ 0
+ 0
+ 0
+
- -
-
-
-
-
-
- Show frame
-
-
-
- -
-
-
- Qt::LeftToRight
-
-
-
-
-
-
- -
-
-
- Color
-
-
-
- -
-
-
- false
-
-
- Line color
-
-
-
- 0
- 0
- 0
-
-
-
-
- -
-
-
- Width
-
-
-
- -
-
-
- false
-
-
- Line width
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
- 0.100000000000000
-
-
- 0.500000000000000
-
-
-
- -
-
-
- Style
-
-
-
- -
-
-
- false
-
-
- Line style
-
-
- 1
-
-
-
-
- NoLine
-
-
- -
-
- Continuous
-
-
- -
-
- Dash
-
-
- -
-
- Dot
-
-
- -
-
- DashDot
-
-
- -
-
- DashDotDot
-
-
-
-
-
-
@@ -256,52 +192,52 @@
- cbShowFrame
+ gbFrame
toggled(bool)
cpFrameColor
setEnabled(bool)
- 183
- 270
+ 250
+ 380
- 183
- 294
+ 380
+ 380
- cbShowFrame
+ gbFrame
toggled(bool)
cFrameStyle
setEnabled(bool)
- 236
- 277
+ 250
+ 380
- 236
- 354
+ 330
+ 380
- cbShowFrame
+ gbFrame
toggled(bool)
dsbWidth
setEnabled(bool)
- 236
- 277
+ 250
+ 380
- 236
- 328
+ 280
+ 380
-
+
\ No newline at end of file
diff --git a/src/Mod/TechDraw/Gui/ViewProviderRichAnno.cpp b/src/Mod/TechDraw/Gui/ViewProviderRichAnno.cpp
index f9e8eb47c4..431a571526 100644
--- a/src/Mod/TechDraw/Gui/ViewProviderRichAnno.cpp
+++ b/src/Mod/TechDraw/Gui/ViewProviderRichAnno.cpp
@@ -57,7 +57,7 @@ const char* ViewProviderRichAnno::LineStyleEnums[] = { "NoLine",
ViewProviderRichAnno::ViewProviderRichAnno()
{
- sPixmap = "actions/TechDraw_RichTextAnnotation";
+ sPixmap = "actions/TechDraw_Annotation";
static const char *group = "Frame Format";
@@ -70,6 +70,21 @@ ViewProviderRichAnno::ViewProviderRichAnno()
}
+bool ViewProviderRichAnno::setEdit(int ModNum)
+{
+ if (ModNum != Gui::ViewProvider::Default) {
+ return Gui::ViewProviderDocumentObject::setEdit(ModNum);
+ }
+ if (Gui::Control().activeDialog()) {
+ return false; // TaskPanel already open!
+ }
+
+ // clear the selection (convenience)
+ Gui::Selection().clearSelection();
+ Gui::Control().showDialog(new TaskDlgRichAnno(this));
+ return true;
+}
+
bool ViewProviderRichAnno::doubleClicked()
{
// Base::Console().message("VPRA::doubleClicked()\n");
@@ -201,3 +216,20 @@ std::vector ViewProviderRichAnno::claimChildren() const
}
return temp;
}
+
+bool ViewProviderRichAnno::onDelete(const std::vector& subs)
+{
+ Q_UNUSED(subs);
+
+ // Check if there is an active dialog
+ if (Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog()) {
+ // Check if the active dialog is our RichAnno dialog
+ if (auto* richAnnoDlg = dynamic_cast(dlg)) {
+ // Check if the dialog is for THIS specific view provider
+ if (richAnnoDlg->isFor(this)) {
+ Gui::Control().closeDialog(); // Close the dialog gracefully
+ }
+ }
+ }
+ return true; // Allow deletion to proceed
+}
\ No newline at end of file
diff --git a/src/Mod/TechDraw/Gui/ViewProviderRichAnno.h b/src/Mod/TechDraw/Gui/ViewProviderRichAnno.h
index d1da2db8c5..cbd80b2b13 100644
--- a/src/Mod/TechDraw/Gui/ViewProviderRichAnno.h
+++ b/src/Mod/TechDraw/Gui/ViewProviderRichAnno.h
@@ -54,8 +54,10 @@ public:
bool useNewSelectionModel() const override {return false;}
void updateData(const App::Property* prop) override;
void onChanged(const App::Property* prop) override;
+ bool onDelete(const std::vector&) override;
bool doubleClicked() override;
bool canDelete(App::DocumentObject* obj) const override;
+ bool setEdit(int ModNum) override;
static const char* LineStyleEnums[];
diff --git a/src/Mod/TechDraw/Gui/Workbench.cpp b/src/Mod/TechDraw/Gui/Workbench.cpp
index 46c1fa0c1c..c2880c365d 100644
--- a/src/Mod/TechDraw/Gui/Workbench.cpp
+++ b/src/Mod/TechDraw/Gui/Workbench.cpp
@@ -387,9 +387,8 @@ Gui::ToolBarItem* Workbench::setupToolBars() const
Gui::ToolBarItem* anno = new Gui::ToolBarItem(root);
anno->setCommand("TechDraw Annotation");
- *anno << "TechDraw_Annotation";
- *anno << "TechDraw_LeaderLine";
*anno << "TechDraw_RichTextAnnotation";
+ *anno << "TechDraw_LeaderLine";
*anno << "TechDraw_CosmeticVertexGroup";
*anno << "TechDraw_CenterLineGroup";
*anno << "TechDraw_2PointCosmeticLine";
diff --git a/src/Mod/TechDraw/Gui/mrichtextedit.cpp b/src/Mod/TechDraw/Gui/mrichtextedit.cpp
index b7fe3f1d61..8fb480ad1f 100644
--- a/src/Mod/TechDraw/Gui/mrichtextedit.cpp
+++ b/src/Mod/TechDraw/Gui/mrichtextedit.cpp
@@ -58,8 +58,12 @@
using namespace TechDrawGui;
using namespace TechDraw;
-MRichTextEdit::MRichTextEdit(QWidget *parent, QString textIn) : QWidget(parent) {
+MRichTextEdit::MRichTextEdit(QWidget *parent, QString textIn) : QFrame(parent) {
setupUi(this);
+
+ f_fontsize->setEditable(true);
+ f_fontsize->setMinimumContentsLength(3);
+
m_lastBlockList = nullptr;
f_textedit->setTabStopDistance(40);
setDefFontSize(TechDrawGui::PreferencesGui::labelFontSizePX());
@@ -228,8 +232,6 @@ MRichTextEdit::MRichTextEdit(QWidget *parent, QString textIn) : QWidget(parent)
int intSize = round(currSize);
QString qsSize = QString::number(intSize);
addFontSize(qsSize);
- int fSize = f_fontsize->findText(qsSize);
- f_fontsize ->setCurrentIndex(fSize);
} else {
QTextCursor cursor = f_textedit->textCursor();
cursor.movePosition(QTextCursor::Start);
@@ -239,10 +241,9 @@ MRichTextEdit::MRichTextEdit(QWidget *parent, QString textIn) : QWidget(parent)
fmt.setFontPointSize(getDefFontSizeNum());
addFontSize(getDefFontSize());
-
- f_fontsize->setCurrentIndex(f_fontsize->findText(getDefFontSize()));
}
+ updateFontSizeDisplay();
}
@@ -486,14 +487,15 @@ void MRichTextEdit::list(bool checked, QTextListFormat::Style style) {
QTextBlockFormat bfmt;
bfmt.setIndent(obfmt.indent());
cursor.setBlockFormat(bfmt);
- } else {
+ }
+ else {
QTextListFormat listFmt;
if (cursor.currentList()) {
listFmt = cursor.currentList()->format();
}
listFmt.setStyle(style);
cursor.createList(listFmt);
- }
+ }
cursor.endEditBlock();
}
@@ -507,17 +509,15 @@ void MRichTextEdit::mergeFormatOnWordOrSelection(const QTextCharFormat &format)
f_textedit->setFocus(Qt::TabFocusReason);
}
-void MRichTextEdit::slotCursorPositionChanged() {
+void MRichTextEdit::slotCursorPositionChanged()
+{
+ updateFontSizeDisplay();
+
//why do we change text style when selecting text?
QTextCursor cursor = f_textedit->textCursor();
if (cursor.hasSelection()) { //let selection logic handle this
return;
}
- //set font size widget to match curr char size
- QTextCharFormat fmt = cursor.charFormat();
- double currSize = fmt.fontPointSize();
- int fSize = f_fontsize->findText(QString::number(currSize));
- f_fontsize ->setCurrentIndex(fSize);
QTextList *l = f_textedit->textCursor().currentList();
@@ -704,20 +704,25 @@ void MRichTextEdit::onExit()
Q_EMIT editorFinished();
}
-void MRichTextEdit::onSelectionChanged()
+void MRichTextEdit::updateFontSizeDisplay()
{
if (hasMultipleSizes()) {
f_fontsize->setEditText(QString());
f_fontsize->setCurrentIndex(-1);
- } else {
+ }
+ else {
QTextCursor cursor = f_textedit->textCursor();
QTextCharFormat fmt = cursor.charFormat();
double currSize = fmt.fontPointSize();
- int fSize = f_fontsize->findText(QString::number(currSize));
- f_fontsize ->setCurrentIndex(fSize);
+ f_fontsize->setEditText(QString::number(currSize, 'g', 4));
}
}
+void MRichTextEdit::onSelectionChanged()
+{
+ updateFontSizeDisplay();
+}
+
//does selection have multiple sizes?
bool MRichTextEdit::hasMultipleSizes()
{
@@ -801,33 +806,55 @@ QFont MRichTextEdit::getDefFont()
// if the conversion factor is float, will generate non-standard sizes
void MRichTextEdit::addFontSize(QString fontSize)
{
+ bool ok;
+ const double newSize = fontSize.toDouble(&ok);
+ if (!ok) {
+ return; // Ignore invalid numbers
+ }
+
+ // 1. Collect all existing sizes as doubles
+ QList sizes;
+ for (int i = 0; i < f_fontsize->count(); ++i) {
+ sizes.append(f_fontsize->itemText(i).toDouble());
+ }
+
+ // 2. Check if the new size is already in the list (using fuzzy comparison for doubles)
+ for (double existingSize : qAsConst(sizes)) {
+ if (qFuzzyCompare(existingSize, newSize)) {
+ // Already exists, just make sure it's the current text
+ f_fontsize->setCurrentText(QString::number(newSize, 'g', 4));
+ return;
+ }
+ }
+
+ // 3. Add the new size and re-sort
+ sizes.append(newSize);
+ std::sort(sizes.begin(), sizes.end());
+
+ // 4. Repopulate the combobox with the sorted, correctly formatted list
QStringList newList;
- int fsVal = fontSize.toInt();
- int size = f_fontsize->count();
- bool added = false;
- for (int i = 0; i < size; i++) {
- QString item = f_fontsize->itemText(i);
- int itemVal = item.toInt();
- if (added) {
- newList << item;
- continue;
- }
- if (itemVal < fsVal) {
- newList << item;
- } else if (itemVal == fsVal) { // already in list
- newList << item;
- added = true;
- } else { //item > fs, add both
- newList << fontSize;
- newList << item;
- added = true;
- }
- }
- if (!added) { //bigger than all choices, add to end of list
- newList << fontSize;
+ for (double size : qAsConst(sizes)) {
+ newList << QString::number(size, 'g', 4);
}
+
+ const QString currentText = f_fontsize->currentText(); // Save current text
+ f_fontsize->blockSignals(true);
f_fontsize->clear();
f_fontsize->addItems(newList);
+ f_fontsize->setCurrentText(currentText); // Restore current text
+ f_fontsize->blockSignals(false);
+
+ // 5. Set the new size as the current item
+ f_fontsize->setCurrentText(fontSize);
+}
+
+void MRichTextEdit::setMinimalMode(bool on)
+{
+ f_save->setVisible(!on);
+ f_exit->setVisible(!on);
+ f_cut->setVisible(!on);
+ f_copy->setVisible(!on);
+ f_paste->setVisible(!on);
}
#include
diff --git a/src/Mod/TechDraw/Gui/mrichtextedit.h b/src/Mod/TechDraw/Gui/mrichtextedit.h
index 741242aaab..2a5104f2d5 100644
--- a/src/Mod/TechDraw/Gui/mrichtextedit.h
+++ b/src/Mod/TechDraw/Gui/mrichtextedit.h
@@ -37,7 +37,7 @@
/**
* @brief A simple rich-text editor
*/
-class MRichTextEdit : public QWidget, protected Ui::MRichTextEdit {
+class MRichTextEdit : public QFrame, protected Ui::MRichTextEdit {
Q_OBJECT
public:
@@ -55,6 +55,8 @@ public:
int getDefFontSizeNum();
QFont getDefFont();
+ void setMinimalMode(bool on);
+
public Q_SLOTS:
void setText(const QString &text);
@@ -72,6 +74,7 @@ protected:
void focusInEvent(QFocusEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
bool hasMultipleSizes();
+ void updateFontSizeDisplay();
void addFontSize(QString fontSize);