From 05131d12389c0d5c1307a297b3c2580ff7c5a50b Mon Sep 17 00:00:00 2001 From: Beat Hangartner Date: Sun, 4 Oct 2026 22:29:47 +0200 Subject: [PATCH] Show crop bars instead of handle points while cropping Black bars on a white outline, an L at each corner and a short bar in the middle of each side, at the same size on screen at any zoom, with the matching resize cursor over each. The handles underneath still take the drags but are no longer drawn. Co-Authored-By: Claude Opus 5.5 --- sources/qetgraphicsitem/diagramimageitem.cpp | 64 ++++++++++++++++++-- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/sources/qetgraphicsitem/diagramimageitem.cpp b/sources/qetgraphicsitem/diagramimageitem.cpp index 59507b77a..8e840e368 100644 --- a/sources/qetgraphicsitem/diagramimageitem.cpp +++ b/sources/qetgraphicsitem/diagramimageitem.cpp @@ -42,6 +42,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -119,12 +122,44 @@ void DiagramImageItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * painter -> drawPixmap(origin, m_crop_preview); painter -> setOpacity(1.0); painter -> drawPixmap(cropFrameLocal(), m_crop_preview, m_pending_crop); - QPen frame(QColor(255, 140, 0)); + const QRectF f = cropFrameLocal(); + QPen frame(Qt::black); frame.setCosmetic(true); - frame.setWidth(2); + frame.setWidth(1); painter -> setPen(frame); painter -> setBrush(Qt::NoBrush); - painter -> drawRect(cropFrameLocal()); + painter -> drawRect(f); + + // Crop bars, as photo editors draw them: an L at each corner and a + // short bar in the middle of each side, black on a white outline + // so they show on dark pictures too, and the same size on screen + // whatever the zoom or the picture's own scale. + const QTransform t = painter -> worldTransform(); + const qreal px = 1.0 / qMax(1e-6, qSqrt(qAbs(t.m11() * t.m22() - t.m12() * t.m21()))); + const qreal corner = qMin(18 * px, qMin(f.width(), f.height()) / 2); + const qreal side = qMin(10 * px, qMin(f.width(), f.height()) / 4); + QPainterPath bars; + auto l = [&bars](QPointF a, QPointF b, QPointF c) { + bars.moveTo(a); bars.lineTo(b); bars.lineTo(c); + }; + l(f.topLeft() + QPointF(0, corner), f.topLeft(), f.topLeft() + QPointF(corner, 0)); + l(f.topRight() - QPointF(corner, 0), f.topRight(), f.topRight() + QPointF(0, corner)); + l(f.bottomRight() - QPointF(0, corner), f.bottomRight(), f.bottomRight() - QPointF(corner, 0)); + l(f.bottomLeft() + QPointF(corner, 0), f.bottomLeft(), f.bottomLeft() - QPointF(0, corner)); + const QPointF c = f.center(); + bars.moveTo(c.x() - side, f.top()); bars.lineTo(c.x() + side, f.top()); + bars.moveTo(c.x() - side, f.bottom()); bars.lineTo(c.x() + side, f.bottom()); + bars.moveTo(f.left(), c.y() - side); bars.lineTo(f.left(), c.y() + side); + bars.moveTo(f.right(), c.y() - side); bars.lineTo(f.right(), c.y() + side); + painter -> setRenderHint(QPainter::Antialiasing, true); + QPen outline(Qt::white, 7, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin); + outline.setCosmetic(true); + painter -> setPen(outline); + painter -> drawPath(bars); + QPen bar(Qt::black, 4, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin); + bar.setCosmetic(true); + painter -> setPen(bar); + painter -> drawPath(bars); painter -> restore(); if (boundary) context->flush(m_adapt_to_dark_theme, painter->worldTransform().mapRect(previewRect) @@ -755,7 +790,7 @@ QColor DiagramImageItem::colorForHandleRole(HandleRole role) case HandleRole::Rotate: return Qt::darkGreen; case HandleRole::SkewEdge: return QColor(255, 140, 0); case HandleRole::Pivot: return Qt::red; - case HandleRole::CropEdge: return QColor(255, 140, 0); + case HandleRole::CropEdge: return Qt::transparent; // paint() draws crop bars instead } return Qt::blue; } @@ -810,6 +845,13 @@ void DiagramImageItem::rebuildHandles() h->setColor(colorForHandleRole(m_handleRoles.at(i))); h->setToolTip(hintForHandleRole(m_handleRoles.at(i))); h->setAcceptHoverEvents(true); + if (m_handleRoles.at(i) == HandleRole::CropEdge) + { + static const Qt::CursorShape cursors[8] = { + Qt::SizeFDiagCursor, Qt::SizeVerCursor, Qt::SizeBDiagCursor, Qt::SizeHorCursor, + Qt::SizeHorCursor, Qt::SizeBDiagCursor, Qt::SizeVerCursor, Qt::SizeFDiagCursor}; + h->setCursor(cursors[i]); + } scene()->addItem(h); h->installSceneEventFilter(this); } @@ -1626,7 +1668,19 @@ QPixmap DiagramImageItem::computeDisplayPixmap(const QPixmap &base, const QRect QRectF DiagramImageItem::boundingRect() const { if (isCropping()) - return imageRect().united(QRectF(-QPointF(m_crop_rect.topLeft()), m_base_pixmap.size())); + { + // Room for the crop bars, which are drawn a few screen pixels + // wide over the edge of the original. + qreal zoom = 1.0; + if (scene() && !scene()->views().isEmpty()) + { + const QTransform t = scene()->views().first()->transform() * sceneTransform(); + zoom = qSqrt(qAbs(t.m11() * t.m22() - t.m12() * t.m21())); + } + const qreal m = 8.0 / qMax(1e-6, zoom); + return imageRect().united(QRectF(-QPointF(m_crop_rect.topLeft()), m_base_pixmap.size())) + .adjusted(-m, -m, m, m); + } if (m_label.isEmpty()) return imageRect(); return imageRect().united(labelRect());