Viewer: paint the resolution readout in drawForeground, not as a scene item

The hovered "d = ... A" readout was a QGraphicsTextItem flagged
ItemIgnoresTransformations, repositioned on every mouse motion. Qt cannot
compute a tight dirty rect for an item that ignores the view transform, so it
marks the entire viewport dirty whenever such an item moves or changes text --
and this one moved constantly.

Paint it in drawForeground() in viewport pixels instead, and repaint only the
union of its old and new rectangles. That also removes the item lifetime
special-casing: it was deliberately kept out of overlay_items_, had to be
nulled by hand after scene()->clear(), and carried comments in three places
warning about the dangling pointer.

This does not reduce raw X11 traffic -- there every repaint uploads the whole
window whatever the damage -- but it cuts the work per hover, and it does
matter under a compressing remote protocol (VNC/NX/xpra), which encodes only
the region that actually changed.

Verified against the previous build: same text, colour and position.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-26 20:13:04 +02:00
co-authored by Claude Opus 5
parent 501ce1ba3d
commit 4892c57119
2 changed files with 49 additions and 47 deletions
+43 -46
View File
@@ -449,8 +449,6 @@ void JFJochDiffractionImage::addCustomOverlay() {
DrawPredictions();
if (show_saturation)
DrawSaturation();
DrawResolutionText();
}
void JFJochDiffractionImage::DrawROIs() {
@@ -916,7 +914,7 @@ void JFJochDiffractionImage::loadImage(std::shared_ptr<const JFJochReaderImage>
scene()->clear();
resetScenePointers();
hover_resolution = NAN;
hover_resolution_item = nullptr;
DrawResolutionText();
CalcROI();
}
@@ -1000,57 +998,56 @@ void JFJochDiffractionImage::setResolutionRingMode(RingMode mode) {
updateOverlay();
}
void JFJochDiffractionImage::DrawResolutionText() {
auto scn = scene();
if (!scn) {
hover_resolution_item = nullptr; // scene gone
return;
}
// Hide item if no valid hover resolution
if (!image || !std::isfinite(hover_resolution) || hover_resolution <= 0.0f) {
if (hover_resolution_item)
hover_resolution_item->setVisible(false);
return;
}
const QRectF visibleRect = mapToScene(viewport()->geometry()).boundingRect();
// Fixed on-screen font size (no dependence on scale_factor)
static QFont HoverResolutionFont() {
QFont font("Arial");
font.setPixelSize(32); // big, constant size on screen
font.setPixelSize(32); // big, constant size on screen
return font;
}
const QString label =
QString("d = %1 Å").arg(QString::number(hover_resolution, 'f', 2));
QString JFJochDiffractionImage::HoverResolutionLabel() const {
if (!image || !std::isfinite(hover_resolution) || hover_resolution <= 0.0f)
return {};
return QString("d = %1 \u00C5").arg(QString::number(hover_resolution, 'f', 2));
}
// Create the item if it does not exist yet; otherwise reuse it
// NOTE: hover_resolution_item is NOT tracked in overlay_items_ — it is persistent
if (!hover_resolution_item) {
hover_resolution_item = scn->addText(label, font);
hover_resolution_item->setZValue(10.0);
// Make the text ignore zooming / view transforms
hover_resolution_item->setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
} else {
hover_resolution_item->setFont(font);
hover_resolution_item->setPlainText(label);
void JFJochDiffractionImage::drawForeground(QPainter *painter, const QRectF &rect) {
JFJochImage::drawForeground(painter, rect);
const QString label = HoverResolutionLabel();
if (label.isEmpty())
return;
painter->save();
painter->resetTransform(); // lay the readout out in viewport pixels, not scene units
painter->setFont(HoverResolutionFont());
painter->setPen(feature_color);
painter->drawText(hover_text_rect_, Qt::AlignLeft | Qt::AlignTop, label);
painter->restore();
}
void JFJochDiffractionImage::DrawResolutionText() {
const QRect previous = hover_text_rect_;
const QString label = HoverResolutionLabel();
if (label.isEmpty())
hover_text_rect_ = QRect();
else {
constexpr int margin_px = 10;
const QFontMetrics fm(HoverResolutionFont());
hover_text_rect_ = QRect(QPoint(margin_px, margin_px), fm.size(0, label));
}
hover_resolution_item->setDefaultTextColor(feature_color);
// Keep a roughly constant ~10 px margin by compensating with scale_factor
const qreal margin_px = 10.0;
const qreal margin_scene = margin_px / std::max(0.0001, scale_factor);
QPointF topLeft(visibleRect.left() + margin_scene,
visibleRect.top() + margin_scene);
hover_resolution_item->setPos(topLeft);
hover_resolution_item->setVisible(true);
// Repaint just the readout. The previous version was a QGraphicsItem flagged
// ItemIgnoresTransformations, which makes Qt mark the whole viewport dirty every time the
// item moves or its text changes - and it moved on every mouse motion.
const QRect dirty = previous.united(hover_text_rect_).adjusted(-2, -2, 2, 2);
if (!dirty.isEmpty())
viewport()->update(dirty);
}
void JFJochDiffractionImage::beforeOverlayCleared() {
// hover_resolution_item is NOT in overlay_items_, so the selective clear won't touch it.
// However, if scene()->clear() is ever called (e.g. on loadImage(nullptr)),
// the caller must also set hover_resolution_item = nullptr separately.
// The resolution readout is painted in drawForeground(), not held as a scene item, so
// clearing the overlay (or the whole scene) cannot leave a dangling pointer behind.
}
void JFJochDiffractionImage::leaveEvent(QEvent *event) {
+6 -1
View File
@@ -21,7 +21,12 @@ Q_OBJECT
QColor second_lattice_color = QColor(0xFA, 0x72, 0x68); // coral, the viewer "finishing" accent
float hover_resolution = NAN;
QGraphicsTextItem* hover_resolution_item = nullptr; // big text in top-left
// The "d = ... A" readout is painted in drawForeground() in viewport pixels rather than kept
// as a scene item, so updating it dirties only its own rect. hover_text_rect_ is where it
// currently sits, in viewport coordinates.
QRect hover_text_rect_;
[[nodiscard]] QString HoverResolutionLabel() const;
void drawForeground(QPainter *painter, const QRectF &rect) override;
public:
enum class RingMode {Auto, Estimation, Manual, None, IceRings};
Q_ENUM(RingMode)