Add an Arc tool and a handle to pull a half arc in or out (#408)

The diagram toolbar gets an "Ajouter un arc" button next to the
ellipse. Click one end, then click the other end at the height the arc
should reach: the result is a half arc bulging up or down from the line
between the two clicks (Shift: a true half circle). It is not filled,
since a fill would close it into a half disc.

A selected half arc shows one more handle, in the middle of the curve.
Dragging it makes the arc deeper or flatter while both ends stay put;
dragging it across the line between the ends turns the arc over. The
handle is hidden whenever the arc is not a half arc on a horizontal or
vertical diameter, because only then is "keep the ends, move the
middle" one well-defined change.

Nothing new is stored: an arc stays an Ellipse with a start and end
angle, exactly as the existing arc handles of an ellipse save it, so
files read by older versions are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-10-01 22:49:35 +13:00
parent 7fdc314e41
commit ffa76a61c1
5 changed files with 128 additions and 3 deletions
+33 -2
View File
@@ -34,10 +34,14 @@
Default constructor
@param diagram : the diagram where this event must operate
@param shape_type : the type of shape to draw
@param half_arc : with shape_type Ellipse, draw a half arc instead of
a whole ellipse (the Arc tool). It is still an Ellipse with a start
and end angle, the same shape the arc handles of an ellipse produce.
*/
DiagramEventAddShape::DiagramEventAddShape(Diagram *diagram, QetShapeItem::ShapeType shape_type) :
DiagramEventAddShape::DiagramEventAddShape(Diagram *diagram, QetShapeItem::ShapeType shape_type, bool half_arc) :
DiagramEventInterface(diagram),
m_shape_type (shape_type),
m_half_arc (half_arc && shape_type == QetShapeItem::Ellipse),
m_shape_item (nullptr),
m_help_horiz (nullptr),
m_help_verti (nullptr)
@@ -131,6 +135,23 @@ void DiagramEventAddShape::applyPosition(const QPointF &pos, Qt::KeyboardModifie
return;
}
if (m_half_arc)
{
// The first click is one end of the arc and sets the height of
// its chord; the cursor sets the other end and how far the arc
// bulges, up or down, from that chord. Shift makes it a true
// half circle.
const qreal width = qAbs(pos.x() - m_anchor_point.x());
qreal height = pos.y() - m_anchor_point.y();
if (mods & Qt::ShiftModifier)
height = (height < 0 ? -width : width) / 2;
const qreal depth = qAbs(height);
m_shape_item->setRect(QRectF(qMin(pos.x(), m_anchor_point.x()), m_anchor_point.y() - depth,
width, 2 * depth));
m_shape_item->setEndAngle(height <= 0 ? 180 : -180);
return;
}
m_center_anchored = (mods & Qt::ControlModifier)
&& (m_shape_type == QetShapeItem::Rectangle || m_shape_type == QetShapeItem::Ellipse);
if (m_center_anchored)
@@ -227,15 +248,20 @@ void DiagramEventAddShape::mousePressEvent(QGraphicsSceneMouseEvent *event)
// live on every subsequent move, from whatever Ctrl state is
// held at the time.
m_center_anchored = (event->modifiers() & Qt::ControlModifier)
&& !m_half_arc
&& (m_shape_type == QetShapeItem::Rectangle || m_shape_type == QetShapeItem::Ellipse);
if (m_center_anchored)
showCenterMarker(m_anchor_point);
if (m_half_arc)
m_shape_item->setEndAngle(180);
//Start from whatever pen/brush was last applied this
//session, rather than always the hardcoded default.
//Not the brush for an arc: it is a line, and a fill
//would close it into a half disc.
if (LastUsedStyle::hasShapePen()) {
m_shape_item->setPen(LastUsedStyle::shapePen());
}
if (LastUsedStyle::hasShapeBrush()) {
if (LastUsedStyle::hasShapeBrush() && !m_half_arc) {
m_shape_item->setBrush(LastUsedStyle::shapeBrush());
}
m_diagram->addItem (m_shape_item);
@@ -461,6 +487,8 @@ QString DiagramEventAddShape::beforeClickHint() const
return tr("Clic gauche : positionner le point de départ (Ctrl = position libre)");
case QetShapeItem::Rectangle:
case QetShapeItem::Ellipse:
if (m_half_arc)
return tr("Clic gauche : positionner une extrémité de l'arc (Ctrl = position libre)");
return tr("Clic gauche : positionner le premier coin (Ctrl = point central, position libre)");
case QetShapeItem::Polygon:
return tr("Clic gauche : positionner le premier point (Ctrl = position libre)");
@@ -479,6 +507,9 @@ QString DiagramEventAddShape::afterClickHint() const
return tr("Clic gauche : positionner le coin opposé (Maj = carré, "
"Ctrl = depuis le centre + position libre, Ctrl+Maj = carré centré) ; clic droit : annuler");
case QetShapeItem::Ellipse:
if (m_half_arc)
return tr("Clic gauche : positionner l'autre extrémité et la hauteur de l'arc "
"(Maj = demi-cercle, Ctrl = position libre) ; clic droit : annuler");
return tr("Clic gauche : positionner le coin opposé (Maj = cercle, "
"Ctrl = depuis le centre + position libre, Ctrl+Maj = cercle centré) ; clic droit : annuler");
case QetShapeItem::Polygon: