TechDraw: Annotation tool rework (#24624)

This commit is contained in:
PaddleStroke
2026-02-24 00:59:15 +00:00
committed by GitHub
parent acb14726b6
commit 3c860396d8
19 changed files with 1528 additions and 649 deletions
+6
View File
@@ -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*/
+37
View File
@@ -20,6 +20,7 @@
* *
***************************************************************************/
#include <Base/Reader.h>
#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<long>("Count");
for (int i = 0; i < propCount; i++) {
reader.readElement("Property");
const char* propName = reader.getAttribute<const char*>("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()) {
+2
View File
@@ -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;
+1 -1
View File
@@ -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)
+80
View File
@@ -26,6 +26,9 @@
# include <QPainter>
# include <QRectF>
# include <QStyleOptionGraphicsItem>
#include <QKeyEvent>
#include <QTextBlock>
#include <QTextCursor>
#include <Base/Console.h>
#include <Base/Parameter.h>
@@ -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 <Mod/TechDraw/Gui/moc_QGCustomText.cpp>
+11
View File
@@ -27,6 +27,7 @@
#include <QGraphicsItem>
#include <QGraphicsTextItem>
#include <QPointF>
#include <QTextCursor>
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;
};
+430 -122
View File
@@ -22,17 +22,27 @@
# include <cmath>
# include <QAbstractTextDocumentLayout>
# include <QDialog>
# include <QGraphicsItem>
# include <QGraphicsSceneMouseEvent>
# include <QPainter>
# include <QRegularExpression>
# include <QApplication>
# include <QCursor>
# include <QRegularExpressionMatch>
# include <QTextBlock>
# include <QTextCursor>
# include <QTextDocument>
# include <QTextDocumentFragment>
# include <QTimer>
# include <QGraphicsScene>
# include <QGraphicsView>
#include <App/Application.h>
#include <Gui/Command.h>
#include <Gui/Control.h>
#include <Mod/TechDraw/App/DrawRichAnno.h>
#include <Mod/TechDraw/App/DrawUtil.h>
@@ -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<TechDraw::DrawRichAnno*>(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<TechDraw::DrawRichAnno *>(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<ViewProviderRichAnno*>(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 <Mod/TechDraw/Gui/moc_QGIRichAnno.cpp>
+74 -16
View File
@@ -19,7 +19,6 @@
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#pragma once
#include <Mod/TechDraw/TechDrawGlobal.h>
@@ -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();
};
}
+5 -1
View File
@@ -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),
+9
View File
@@ -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()) {
File diff suppressed because it is too large Load Diff
+49 -31
View File
@@ -26,9 +26,8 @@
#include <Gui/TaskView/TaskDialog.h>
#include <Gui/TaskView/TaskView.h>
#include <Mod/TechDraw/TechDrawGlobal.h>
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_TaskRichAnno> 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<MRichTextEdit> m_toolbar {nullptr};
QPointer<QWidget> 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;
+152 -216
View File
@@ -6,22 +6,10 @@
<rect>
<x>0</x>
<y>0</y>
<width>409</width>
<height>419</height>
<width>150</width>
<height>80</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>250</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Rich Text Annotation Block</string>
</property>
@@ -31,209 +19,157 @@
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QFrame" name="frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<widget class="QLabel" name="label_BaseFeature">
<property name="text">
<string>Base feature</string>
</property>
<property name="frameShape">
<enum>QFrame::Box</enum>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="leBaseView">
<property name="enabled">
<bool>false</bool>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_MaxWidth">
<property name="text">
<string>Width</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
</widget>
</item>
<item row="3" column="1">
<widget class="Gui::QuantitySpinBox" name="dsbMaxWidth">
<property name="toolTip">
<string>Maximal width of the annotation block, if -1 then automatic width</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="minimum">
<double>-1.000000000000000</double>
</property>
<property name="value">
<double>-1.000000000000000</double>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QGroupBox" name="gbFrame">
<property name="title">
<string>Frame</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_FrameProperties">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item row="0" column="0">
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Base feature</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="leBaseView">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Max width</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="Gui::QuantitySpinBox" name="dsbMaxWidth">
<property name="toolTip">
<string>Maximal width, if -1 then automatic width</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="minimum">
<double>-1.000000000000000</double>
</property>
<property name="value">
<double>-1.000000000000000</double>
</property>
</widget>
</item>
</layout>
<widget class="QLabel" name="label_frameThickness">
<property name="text">
<string>Thickness</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="Gui::QuantitySpinBox" name="dsbWidth">
<property name="toolTip">
<string>Line width</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>0.500000000000000</double>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<widget class="QLabel" name="label_patternThickness">
<property name="text">
<string>Style</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="cFrameStyle">
<property name="toolTip">
<string>Line style</string>
</property>
<property name="currentIndex">
<number>1</number>
</property>
<item>
<property name="text">
<string>NoLine</string>
</property>
</item>
<item>
<property name="text">
<string>Continuous</string>
</property>
</item>
<item>
<property name="text">
<string>Dash</string>
</property>
</item>
<item>
<property name="text">
<string>Dot</string>
</property>
</item>
<item>
<property name="text">
<string>DashDot</string>
</property>
</item>
<item>
<property name="text">
<string>DashDotDot</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QPushButton" name="pbEditor">
<property name="enabled">
<bool>true</bool>
</property>
<widget class="QLabel" name="label_colorThickness">
<property name="text">
<string>Start Rich Text Editor</string>
<string>Color</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QTextEdit" name="teAnnoText">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<item row="2" column="1">
<widget class="Gui::ColorButton" name="cpFrameColor">
<property name="toolTip">
<string>Line color</string>
</property>
<property name="color">
<color>
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</property>
</widget>
</item>
<item row="4" column="0">
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Show frame</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="cbShowFrame">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Color</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="Gui::ColorButton" name="cpFrameColor">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Line color</string>
</property>
<property name="color">
<color>
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Width</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="Gui::QuantitySpinBox" name="dsbWidth">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Line width</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>0.500000000000000</double>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Style</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="cFrameStyle">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Line style</string>
</property>
<property name="currentIndex">
<number>1</number>
</property>
<item>
<property name="text">
<string>NoLine</string>
</property>
</item>
<item>
<property name="text">
<string>Continuous</string>
</property>
</item>
<item>
<property name="text">
<string>Dash</string>
</property>
</item>
<item>
<property name="text">
<string>Dot</string>
</property>
</item>
<item>
<property name="text">
<string>DashDot</string>
</property>
</item>
<item>
<property name="text">
<string>DashDotDot</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
@@ -256,52 +192,52 @@
</resources>
<connections>
<connection>
<sender>cbShowFrame</sender>
<sender>gbFrame</sender>
<signal>toggled(bool)</signal>
<receiver>cpFrameColor</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>183</x>
<y>270</y>
<x>250</x> <!-- Adjust X hint if needed -->
<y>380</y> <!-- Adjust Y hint if needed -->
</hint>
<hint type="destinationlabel">
<x>183</x>
<y>294</y>
<x>380</x> <!-- Adjust X hint if needed -->
<y>380</y> <!-- Adjust Y hint if needed -->
</hint>
</hints>
</connection>
<connection>
<sender>cbShowFrame</sender>
<sender>gbFrame</sender>
<signal>toggled(bool)</signal>
<receiver>cFrameStyle</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>236</x>
<y>277</y>
<x>250</x>
<y>380</y>
</hint>
<hint type="destinationlabel">
<x>236</x>
<y>354</y>
<x>330</x> <!-- Adjust X hint if needed -->
<y>380</y>
</hint>
</hints>
</connection>
<connection>
<sender>cbShowFrame</sender>
<sender>gbFrame</sender>
<signal>toggled(bool)</signal>
<receiver>dsbWidth</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>236</x>
<y>277</y>
<x>250</x>
<y>380</y>
</hint>
<hint type="destinationlabel">
<x>236</x>
<y>328</y>
<x>280</x> <!-- Adjust X hint if needed -->
<y>380</y>
</hint>
</hints>
</connection>
</connections>
</ui>
</ui>
+33 -1
View File
@@ -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<App::DocumentObject*> ViewProviderRichAnno::claimChildren() const
}
return temp;
}
bool ViewProviderRichAnno::onDelete(const std::vector<std::string>& 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<TaskDlgRichAnno*>(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
}
@@ -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<std::string>&) override;
bool doubleClicked() override;
bool canDelete(App::DocumentObject* obj) const override;
bool setEdit(int ModNum) override;
static const char* LineStyleEnums[];
+1 -2
View File
@@ -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";
+67 -40
View File
@@ -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<double> 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 <Mod/TechDraw/Gui/moc_mrichtextedit.cpp>
+4 -1
View File
@@ -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);