View Issue Details

IDProjectCategoryView StatusLast Update
0017870ScribusTablespublic2026-09-08 13:40
Reporterbuttar Assigned To 
PrioritynormalSeverityminorReproducibilityalways
Status newResolutionopen 
PlatformLinuxOSFedora WorkstationOS Version44
Product Version1.7.4.svn 
Summary0017870: Cannot draw tables quickly (for initial size, when creating)
DescriptionSlow is fine.

See attached clip.
Steps To ReproduceMove the mouse a bit fast when drawing a table
Additional Information(Works fine in 1.7.2 flatpak.)
Tags#cant reproduce, #please_test
Attached Files
PatchNo

Activities

cbradney

2026-07-03 21:07

administrator   ~0053939

Works fine here on macOS

buttar

2026-07-04 05:32

reporter   ~0053943

I just found out that this issue is not table specific. Any frame I draw with a sudden/quick mouse movement has this issue: text- and image frames, shape, polygon, spiral.

ale

2026-07-04 07:25

manager   ~0053949

It's probably related to your mouse...

Works for me too

buttar

2026-07-04 08:00

reporter   ~0053954

I don't think it is related to my mouse because I tested:
1. with my other (wired) mouse in 1.7.4.svn and things do not work (as described originally).
2. with both mice in the 1.7.2 flatpak and things work just fine.

buttar

2026-07-06 04:45

reporter   ~0053983

Cannot reproduce it on Linux Mint (LM) 22.2. Might be related to some other packages Fedora uses while those on LM can be a slightly old.

PeterBenedek

2026-07-06 05:51

developer   ~0053986

I can't reproduce on Linux Mint 22.3
Works for me too

buttar

2026-09-08 07:52

reporter   ~0054435

IT turned out to be a real issue and the details below would make better sense to you.


#Issue
  -- With Move/Resize Value Indicator enabled, rapid canvas drags can keep receiving mouse-move events while the active canvas mode rejects them before geometry update, freezing the live interaction while the pointer continues moving.
  -- Image Import used the same tooltip-based mouse-following mechanism.

#Cause
  -- Live canvas feedback repeatedly called `QToolTip::showText()` from mouse-move paths.
  -- This could interfere with the button state seen by canvas modes.
  -- `QToolTip` is appropriate for transient hover help, but using it as a continuously updated drag-time HUD was effectively a misuse of the widget for this interaction pattern.

#Fix
  -- Replaces drag-time canvas tooltips with a shared canvas-owned, mouse-transparent HUD overlay.
  -- Routes existing `Canvas::display*HUD()` readouts through it.
  -- Migrates Image Import preview to the same HUD and renders its thumbnail directly instead of repeatedly rebuilding tooltip HTML.
  -- Leaves genuine hover tooltips unchanged.

#HUD behaviour
  -- Stays visible for the full active interaction.
  -- Uses a 150 ms post-release linger; movement beyond 5 px hides it immediately.
  -- Flips/clamps against the visible viewport with a 5 px safety margin.
  -- Uses tooltip-derived palette/font with lightweight badge styling.
  -- Image Import uses explicit mode lifecycle instead of the measurement linger timer.

#Result
  -- Fixes the fast-drag freeze without special-casing `CreateMode`.
  -- Replaces the tooltip misuse with an interaction-appropriate shared canvas HUD.
  -- Centralizes live canvas feedback in one reusable path.
  -- Tested with frame creation, move/resize, rotation, guides, tables, lines, and Image Import.

Patch included (canvas-hud-drag-fix-v1.05.patch).

(Change the title of the ticket also? Maybe "Canvas drag updates freeze with Move/Resize Value Indicator enabled")
canvas-hud-drag-fix-v1.05.patch (17,143 bytes)   
Index: scribus/canvas.h
===================================================================
--- scribus/canvas.h	(revision 27824)
+++ scribus/canvas.h	(working copy)
@@ -39,6 +39,9 @@
 #include "pageitempointer.h"
 
 
+class QLabel;
+class QTimer;
+
 class ScPage;
 class PageItem;
 class ScLayer;
@@ -208,11 +211,16 @@
 	void displayRotHUD(QPointF m, double rot);
 	void displayRealRotHUD(QPointF m, double rot);
 	/**
-	 * Displays a tooltip of the format <code>{label}: {N} {unit}</code> where <code>N</code> is
+	 * Displays a HUD of the format <code>{label}: {N} {unit}</code> where <code>N</code> is
 	 * @a value converted to the current document unit and <code>unit</code> the current document
-	 * unit. The tooltip will be displayed nearby the global point @a point.
+	 * unit. The HUD will be displayed nearby the global point @a point.
 	 */
 	void displayDoubleHUD(QPointF point, const QString& label, double value);
+	void displayHUD(QPointF globalPos, const QString& text, bool autoHide = true);
+	void displayHUD(QPointF globalPos, const QPixmap& pixmap, bool autoHide = true);
+	void beginHUDLinger(QPointF globalPos);
+	void handleHUDMouseMove(QPointF globalPos);
+	void hideHUD();
 
 	void setupEditHRuler(PageItem * item, bool forceAndReset = false);
 	
@@ -263,6 +271,9 @@
 	QPixmap createPixmap(double w, double h);
 	// draw a potentially hidpi pixmap
 	void drawPixmap(QPainter &painter, double x, double y, const QPixmap &pixmap, double sx, double sy, double sw, double sh);
+	void positionHUD(QPointF globalPos, bool autoHide);
+	void updateHUDStyle();
+	void changeEvent(QEvent* event) override;
 		
 private:
 	ScribusDoc* m_doc;
@@ -275,6 +286,11 @@
 	QPixmap m_selectionBuffer;
 	QRect   m_selectionRect;
 	QPoint  m_oldMinCanvasCoordinate;
+	QLabel* m_hudLabel { nullptr };
+	QTimer* m_hudHideTimer { nullptr };
+	bool m_hudAutoHide { false };
+	bool m_hudLingering { false };
+	QPointF m_hudLingerOrigin;
 };
 
 
Index: scribus/canvas.cpp
===================================================================
--- scribus/canvas.cpp	(revision 27824)
+++ scribus/canvas.cpp	(working copy)
@@ -19,6 +19,11 @@
 #include <cmath>
 
 // #include <QDebug>
+#include <QEvent>
+#include <QLabel>
+#include <QPainter>
+#include <QPalette>
+#include <QTimer>
 #include <QToolTip>
 #include <QWidget>
 
@@ -47,6 +52,28 @@
 	return p;
 }
 
+constexpr int HUD_HIDE_DELAY_MS = 150;
+constexpr double HUD_MOVE_GRACE_PX = 5.0;
+
+class CanvasHUDLabel final : public QLabel
+{
+public:
+	using QLabel::QLabel;
+
+protected:
+	void paintEvent(QPaintEvent* event) override
+	{
+		{
+			QPainter painter(this);
+			painter.setRenderHint(QPainter::Antialiasing, true);
+			painter.setPen(palette().color(QPalette::Mid));
+			painter.setBrush(palette().brush(QPalette::Window));
+			painter.drawRoundedRect(QRectF(rect()).adjusted(0.5, 0.5, -0.5, -0.5), 3.0, 3.0);
+		}
+		QLabel::paintEvent(event);
+	}
+};
+
 QDataStream &operator<< ( QDataStream & ds, const CanvasViewMode & vm )
 {
 	ds << vm.scale
@@ -92,6 +119,17 @@
 	m_buffer = QPixmap();
 	m_bufferRect = QRect();
 	m_renderMode = RENDER_NORMAL;
+
+	m_hudLabel = new CanvasHUDLabel(this);
+	m_hudLabel->setAttribute(Qt::WA_TransparentForMouseEvents, true);
+	m_hudLabel->setFocusPolicy(Qt::NoFocus);
+	m_hudLabel->setContentsMargins(6, 4, 6, 4);
+	updateHUDStyle();
+	m_hudLabel->hide();
+
+	m_hudHideTimer = new QTimer(this);
+	m_hudHideTimer->setSingleShot(true);
+	connect(m_hudHideTimer, &QTimer::timeout, this, &Canvas::hideHUD);
 }
 
 void Canvas::setPreviewVisual(int mode)
@@ -2240,10 +2278,111 @@
 	painter.drawPixmap(x, y, pixmap, sx, sy, sw, sh);
 }
 
+void Canvas::displayHUD(QPointF globalPos, const QString& text, bool autoHide)
+{
+	m_hudLabel->setPixmap(QPixmap());
+	m_hudLabel->setText(text);
+	positionHUD(globalPos, autoHide);
+}
+
+void Canvas::displayHUD(QPointF globalPos, const QPixmap& pixmap, bool autoHide)
+{
+	m_hudLabel->clear();
+	m_hudLabel->setPixmap(pixmap);
+	positionHUD(globalPos, autoHide);
+}
+
+void Canvas::beginHUDLinger(QPointF globalPos)
+{
+	if (!m_hudAutoHide || !m_hudLabel->isVisible())
+		return;
+
+	m_hudLingering = true;
+	m_hudLingerOrigin = globalPos;
+	m_hudHideTimer->start(HUD_HIDE_DELAY_MS);
+}
+
+void Canvas::handleHUDMouseMove(QPointF globalPos)
+{
+	if (!m_hudLingering || !m_hudLabel->isVisible())
+		return;
+
+	const QPointF delta = globalPos - m_hudLingerOrigin;
+	if (delta.x() * delta.x() + delta.y() * delta.y() > HUD_MOVE_GRACE_PX * HUD_MOVE_GRACE_PX)
+		hideHUD();
+}
+
+void Canvas::hideHUD()
+{
+	m_hudHideTimer->stop();
+	m_hudLingering = false;
+	m_hudAutoHide = false;
+	m_hudLabel->hide();
+}
+
+void Canvas::positionHUD(QPointF globalPos, bool autoHide)
+{
+	constexpr int hudOffsetX = 12;
+	constexpr int hudOffsetY = 16;
+	constexpr int hudViewportPadding = 5;
+
+	m_hudLabel->adjustSize();
+
+	QWidget* viewport = m_view->viewport();
+	const QRect visibleRect(viewport->mapToGlobal(QPoint(0, 0)), viewport->size());
+	const QRect hudBounds = visibleRect.adjusted(hudViewportPadding, hudViewportPadding, -hudViewportPadding, -hudViewportPadding);
+	const QPoint anchor = globalPos.toPoint();
+	QPoint hudGlobalPos = anchor + QPoint(hudOffsetX, hudOffsetY);
+	if (hudGlobalPos.x() + m_hudLabel->width() > hudBounds.right() + 1)
+		hudGlobalPos.setX(anchor.x() - m_hudLabel->width() - hudOffsetX);
+	if (hudGlobalPos.y() + m_hudLabel->height() > hudBounds.bottom() + 1)
+		hudGlobalPos.setY(anchor.y() - m_hudLabel->height() - hudOffsetY);
+
+	const int maxX = qMax(hudBounds.left(), hudBounds.right() - m_hudLabel->width() + 1);
+	const int maxY = qMax(hudBounds.top(), hudBounds.bottom() - m_hudLabel->height() + 1);
+	hudGlobalPos.setX(qBound(hudBounds.left(), hudGlobalPos.x(), maxX));
+	hudGlobalPos.setY(qBound(hudBounds.top(), hudGlobalPos.y(), maxY));
+	m_hudLabel->move(mapFromGlobal(hudGlobalPos));
+	if (!m_hudLabel->isVisible())
+	{
+		m_hudLabel->show();
+		m_hudLabel->raise();
+	}
+
+	m_hudAutoHide = autoHide;
+	m_hudLingering = false;
+	if (autoHide && !m_viewMode.m_MouseButtonPressed)
+		m_hudHideTimer->start(HUD_HIDE_DELAY_MS);
+	else
+		m_hudHideTimer->stop();
+}
+
+void Canvas::updateHUDStyle()
+{
+	if (!m_hudLabel)
+		return;
+
+	QPalette hudPalette = QToolTip::palette();
+	hudPalette.setBrush(QPalette::Window, hudPalette.brush(QPalette::ToolTipBase));
+	hudPalette.setBrush(QPalette::WindowText, hudPalette.brush(QPalette::ToolTipText));
+	m_hudLabel->setPalette(hudPalette);
+	m_hudLabel->setFont(QToolTip::font());
+}
+
+void Canvas::changeEvent(QEvent* event)
+{
+	QWidget::changeEvent(event);
+	if (event->type() == QEvent::PaletteChange || event->type() == QEvent::FontChange || event->type() == QEvent::StyleChange)
+		updateHUDStyle();
+}
+
 void Canvas::displayXYHUD(QPointF m)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
+	}
 	double gx, gy, gh, gw, r;
 	if (m_doc->m_Selection->isMultipleSelection())
 	{
@@ -2288,7 +2427,10 @@
 void Canvas::displayCorrectedXYHUD(QPointF m, double x, double y)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
+	}
 	double gx = x;
 	double gy = y;
 	if (m_doc->guidesPrefs().rulerMode)
@@ -2299,81 +2441,91 @@
 	gx -= m_doc->rulerXoffset;
 	gy -= m_doc->rulerYoffset;
 
-	QPointF tooltipPos = m + QPointF(5.0, 5.0);
-	QToolTip::showText(tooltipPos.toPoint(), tr("X: %1\nY: %2").arg(value2String(gx, m_doc->unitIndex(), true, true), value2String(gy, m_doc->unitIndex(), true, true)), this);
+	displayHUD(m, tr("X: %1\nY: %2").arg(value2String(gx, m_doc->unitIndex(), true, true), value2String(gy, m_doc->unitIndex(), true, true)));
 }
 
 void Canvas::displayCorrectedSingleHUD(QPointF m, double val, bool isX)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
+	}
 	double gx = val;
 	if (isX)
 	{
 		if (m_doc->guidesPrefs().rulerMode)
 			gx -= m_doc->currentPage()->xOffset();
 		gx -= m_doc->rulerXoffset;
-		QPointF tooltipPos = m + QPointF(5.0, 5.0);
-		QToolTip::showText(tooltipPos.toPoint(), tr("X: %1").arg(value2String(gx, m_doc->unitIndex(), true, true)), this);
+		displayHUD(m, tr("X: %1").arg(value2String(gx, m_doc->unitIndex(), true, true)));
 	}
 	else
 	{
 		if (m_doc->guidesPrefs().rulerMode)
 			gx -= m_doc->currentPage()->yOffset();
 		gx -= m_doc->rulerYoffset;
-		QPointF tooltipPos = m + QPointF(5.0, 5.0);
-		QToolTip::showText(tooltipPos.toPoint(), tr("Y: %1").arg(value2String(gx, m_doc->unitIndex(), true, true)), this);
+		displayHUD(m, tr("Y: %1").arg(value2String(gx, m_doc->unitIndex(), true, true)));
 	}
 }
 
 void Canvas::displayXYHUD(QPointF m, double x, double y)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
+	}
 
-	QPointF tooltipPos = m + QPointF(5.0, 5.0);
-	QToolTip::showText(tooltipPos.toPoint(), tr("X: %1\nY: %2").arg(value2String(x, m_doc->unitIndex(), true, true), value2String(y, m_doc->unitIndex(), true, true)), this);
+	displayHUD(m, tr("X: %1\nY: %2").arg(value2String(x, m_doc->unitIndex(), true, true), value2String(y, m_doc->unitIndex(), true, true)));
 }
 
 void Canvas::displaySizeHUD(QPointF m, double x, double y, bool isLine)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
+	}
 
-	QPointF tooltipPos = m + QPointF(5.0, 5.0);
 	if (isLine)
-		QToolTip::showText(tooltipPos.toPoint(), tr("Length: %1\nAngle: %2").arg(value2String(x, m_doc->unitIndex(), true, true), value2String(y, SC_DEGREES, true, true)), this);
+		displayHUD(m, tr("Length: %1\nAngle: %2").arg(value2String(x, m_doc->unitIndex(), true, true), value2String(y, SC_DEGREES, true, true)));
 	else
-		QToolTip::showText(tooltipPos.toPoint(), tr("Width: %1\nHeight: %2").arg(value2String(x, m_doc->unitIndex(), true, true), value2String(y, m_doc->unitIndex(), true, true)), this);
+		displayHUD(m, tr("Width: %1\nHeight: %2").arg(value2String(x, m_doc->unitIndex(), true, true), value2String(y, m_doc->unitIndex(), true, true)));
 }
 
 void Canvas::displayRotHUD(QPointF m, double rot)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
+	}
 	double r;
 	if (rot < 0.0)
 		r = rot * -1.0;
 	else
 		r = 360.0 - rot;
-	QPointF tooltipPos = m + QPointF(5.0, 5.0);
-	QToolTip::showText(tooltipPos.toPoint(), tr("Angle: %1").arg(value2String(r, SC_DEGREES, true, true)), this);
+	displayHUD(m, tr("Angle: %1").arg(value2String(r, SC_DEGREES, true, true)));
 }
 
 void Canvas::displayRealRotHUD(QPointF m, double rot)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
-	QPointF tooltipPos = m + QPointF(5.0, 5.0);
-	QToolTip::showText(tooltipPos.toPoint(), tr("Angle: %1").arg(value2String(rot, SC_DEGREES, true, true)), this);
+	}
+	displayHUD(m, tr("Angle: %1").arg(value2String(rot, SC_DEGREES, true, true)));
 }
 
 void Canvas::displayDoubleHUD(QPointF point, const QString& label, double value)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
-	QPointF tooltipPos = point + QPointF(5.0, 5.0);
-	QToolTip::showText(tooltipPos.toPoint(), QString("%1: %2").arg(label, value2String(value, m_doc->unitIndex(), true, true)), this);
+	}
+	displayHUD(point, QString("%1: %2").arg(label, value2String(value, m_doc->unitIndex(), true, true)));
 }
 
 void Canvas::setupEditHRuler(PageItem * item, bool forceAndReset)
Index: scribus/canvasmode_imageimport.h
===================================================================
--- scribus/canvasmode_imageimport.h	(revision 27824)
+++ scribus/canvasmode_imageimport.h	(working copy)
@@ -55,12 +55,14 @@
 
 private:
 	bool m_keyRepeat {false};
-	QString m_tipText;
+	QPixmap m_previewPixmap;
+	QString m_previewText;
 	QStringList m_imageList;
 	ScribusMainWindow *m_ScMW {nullptr};
 	double m_Mx {0.0};
 	double m_My {0.0};
-	void newToolTip(const QString& name);
+	void displayPreview(QPointF globalPos);
+	void updatePreview(const QString& name);
 };
 
 #endif // CANVASMODE_IMAGEIMPORT_H
Index: scribus/canvasmode_imageimport.cpp
===================================================================
--- scribus/canvasmode_imageimport.cpp	(revision 27824)
+++ scribus/canvasmode_imageimport.cpp	(working copy)
@@ -16,7 +16,6 @@
 
 
 #include <QApplication>
-#include <QBuffer>
 #include <QCursor>
 #include <QEvent>
 #include <QMouseEvent>
@@ -24,7 +23,6 @@
 #include <QPixmap>
 #include <QPoint>
 #include <QRect>
-#include <QToolTip>
 #include <QDebug>
 
 #include "canvas.h"
@@ -38,6 +36,9 @@
 
 CanvasMode_ImageImport::CanvasMode_ImageImport(ScribusView *view) : CanvasMode(view), m_ScMW(view->m_ScMW)
 {
+	const QPoint cursorPos = QCursor::pos();
+	m_Mx = cursorPos.x();
+	m_My = cursorPos.y();
 }
 
 void CanvasMode_ImageImport::setImageList(QStringList l)
@@ -53,36 +54,48 @@
 			m_view->requestMode(submodePaintingDone);
 		}
 		else
-			newToolTip(l.first());
+			updatePreview(l.first());
 	}
 	else
-		newToolTip(l.first());
+		updatePreview(l.first());
 }
 
-void CanvasMode_ImageImport::newToolTip(const QString& name)
+void CanvasMode_ImageImport::displayPreview(QPointF globalPos)
 {
-	QImage pm(80, 80, QImage::Format_ARGB32_Premultiplied);
-	QBrush b(QColor(205,205,205), IconManager::instance().loadPixmap("testfill"));
-	
-	QPainter p;
-	p.begin(&pm);
-	p.fillRect(QRectF(0, 0, 80, 80), b);
+	if (!m_previewPixmap.isNull())
+		m_canvas->displayHUD(globalPos, m_previewPixmap, false);
+	else
+		m_canvas->displayHUD(globalPos, m_previewText, false);
+}
+
+void CanvasMode_ImageImport::updatePreview(const QString& name)
+{
+	QPixmap preview(80, 80);
+	QBrush background(QColor(205, 205, 205), IconManager::instance().loadPixmap("testfill"));
 	QImage thumb;
-	if (thumb.load(name))
+	const bool hasThumbnail = thumb.load(name);
+	{
+		QPainter painter(&preview);
+		painter.fillRect(QRectF(0, 0, 80, 80), background);
+		if (hasThumbnail)
+		{
+			thumb = thumb.scaled(80, 80, Qt::KeepAspectRatio);
+			painter.drawImage((80 - thumb.width()) / 2, (80 - thumb.height()) / 2, thumb);
+		}
+	}
+
+	if (hasThumbnail)
 	{
-		thumb = thumb.scaled(80,80,Qt::KeepAspectRatio);
-		p.drawImage((80 - thumb.width()) / 2, (80 - thumb.height()) / 2, thumb);
-		QBuffer buffer;
-		buffer.open(QIODevice::WriteOnly);
-		pm.save(&buffer, "PNG");
-		QByteArray ba = buffer.buffer().toBase64();
-		buffer.close();
-		m_tipText = "<p align=\"center\"><img src=\"data:image/png;base64," + QString(ba) + "\"></p>";
+		m_previewPixmap = preview;
+		m_previewText.clear();
 	}
 	else
-		m_tipText = "<p align=\"center\">" + name.right(name.lastIndexOf("/")) + "</p>";
-	QToolTip::showText(QPoint(m_Mx,m_My), m_tipText, qApp->activeWindow());
-	p.end();
+	{
+		m_previewPixmap = QPixmap();
+		m_previewText = name.right(name.lastIndexOf("/"));
+	}
+
+	displayPreview(QPointF(m_Mx, m_My));
 }
 
 void CanvasMode_ImageImport::drawControls(QPainter* p)
@@ -125,6 +138,7 @@
 
 void CanvasMode_ImageImport::leaveEvent(QEvent *e)
 {
+	m_canvas->hideHUD();
 }
 
 void CanvasMode_ImageImport::activate(bool fromGesture)
@@ -146,6 +160,7 @@
 
 void CanvasMode_ImageImport::deactivate(bool forGesture)
 {
+	m_canvas->hideHUD();
 	m_view->setRedrawMarkerShown(false);
 	CanvasMode::deactivate(forGesture);
 }
@@ -161,8 +176,7 @@
 {
 	m_Mx = m->globalPosition().x();
 	m_My = m->globalPosition().y();
-	QToolTip::showText(m->globalPosition().toPoint(), m_tipText, qApp->activeWindow());
-	QToolTip::showText(m->globalPosition().toPoint(), m_tipText + "<b></b>", qApp->activeWindow());
+	displayPreview(m->globalPosition());
 	m->accept();
 	PageItem *item;
 	if ((item = m_canvas->itemUnderCursor(m->globalPosition())) != nullptr)
@@ -249,8 +263,8 @@
 	if (m_imageList.isEmpty())
 	{
 		m_view->requestMode(submodePaintingDone);
-		QToolTip::hideText();
+		m_canvas->hideHUD();
 	}
 	else
-		newToolTip(m_imageList.constFirst());
+		updatePreview(m_imageList.constFirst());
 }
Index: scribus/scribusview.cpp
===================================================================
--- scribus/scribusview.cpp	(revision 27824)
+++ scribus/scribusview.cpp	(working copy)
@@ -3369,6 +3369,7 @@
 		emit MousePos(p.x(),p.y());
 		horizRuler->draw(m->position().x() + qRound(m_doc->minCanvasCoordinate.x() * m_canvas->scale()));
 		vertRuler->draw(m->position().y() + qRound(m_doc->minCanvasCoordinate.y() * m_canvas->scale()));
+		m_canvas->handleHUDMouseMove(m->globalPosition());
 		m_canvasMode->mouseMoveEvent(m);
 		return true;
 	}
@@ -3377,6 +3378,7 @@
 		auto* m = dynamic_cast<QMouseEvent*> (event);
 		m_canvasMode->mouseReleaseEvent(m);
 		m_canvas->m_viewMode.m_MouseButtonPressed = false;
+		m_canvas->beginHUDLinger(m->globalPosition());
 		if (linkAfterDraw)
 		{
 			//user creates new frame using linking tool
canvas-hud-drag-fix-v1.05.patch (17,143 bytes)   

buttar

2026-09-08 07:54

reporter   ~0054436

Even just turning off the `View > Measurement > Move/Resize Value` Indicator solved it instantly.

buttar

2026-09-08 08:10

reporter   ~0054437

Here are two clips showing old and new HUD.

buttar

2026-09-08 09:25

reporter   ~0054438

A perceivable border, if you will.
canvas-hud-drag-fix-v1.06.patch (17,222 bytes)   
Index: scribus/canvas.h
===================================================================
--- scribus/canvas.h	(revision 27824)
+++ scribus/canvas.h	(working copy)
@@ -39,6 +39,9 @@
 #include "pageitempointer.h"
 
 
+class QLabel;
+class QTimer;
+
 class ScPage;
 class PageItem;
 class ScLayer;
@@ -208,11 +211,16 @@
 	void displayRotHUD(QPointF m, double rot);
 	void displayRealRotHUD(QPointF m, double rot);
 	/**
-	 * Displays a tooltip of the format <code>{label}: {N} {unit}</code> where <code>N</code> is
+	 * Displays a HUD of the format <code>{label}: {N} {unit}</code> where <code>N</code> is
 	 * @a value converted to the current document unit and <code>unit</code> the current document
-	 * unit. The tooltip will be displayed nearby the global point @a point.
+	 * unit. The HUD will be displayed nearby the global point @a point.
 	 */
 	void displayDoubleHUD(QPointF point, const QString& label, double value);
+	void displayHUD(QPointF globalPos, const QString& text, bool autoHide = true);
+	void displayHUD(QPointF globalPos, const QPixmap& pixmap, bool autoHide = true);
+	void beginHUDLinger(QPointF globalPos);
+	void handleHUDMouseMove(QPointF globalPos);
+	void hideHUD();
 
 	void setupEditHRuler(PageItem * item, bool forceAndReset = false);
 	
@@ -263,6 +271,9 @@
 	QPixmap createPixmap(double w, double h);
 	// draw a potentially hidpi pixmap
 	void drawPixmap(QPainter &painter, double x, double y, const QPixmap &pixmap, double sx, double sy, double sw, double sh);
+	void positionHUD(QPointF globalPos, bool autoHide);
+	void updateHUDStyle();
+	void changeEvent(QEvent* event) override;
 		
 private:
 	ScribusDoc* m_doc;
@@ -275,6 +286,11 @@
 	QPixmap m_selectionBuffer;
 	QRect   m_selectionRect;
 	QPoint  m_oldMinCanvasCoordinate;
+	QLabel* m_hudLabel { nullptr };
+	QTimer* m_hudHideTimer { nullptr };
+	bool m_hudAutoHide { false };
+	bool m_hudLingering { false };
+	QPointF m_hudLingerOrigin;
 };
 
 
Index: scribus/canvas.cpp
===================================================================
--- scribus/canvas.cpp	(revision 27824)
+++ scribus/canvas.cpp	(working copy)
@@ -19,6 +19,11 @@
 #include <cmath>
 
 // #include <QDebug>
+#include <QEvent>
+#include <QLabel>
+#include <QPainter>
+#include <QPalette>
+#include <QTimer>
 #include <QToolTip>
 #include <QWidget>
 
@@ -47,6 +52,30 @@
 	return p;
 }
 
+constexpr int HUD_HIDE_DELAY_MS = 150;
+constexpr double HUD_MOVE_GRACE_PX = 5.0;
+
+class CanvasHUDLabel final : public QLabel
+{
+public:
+	using QLabel::QLabel;
+
+protected:
+	void paintEvent(QPaintEvent* event) override
+	{
+		{
+			QPainter painter(this);
+			painter.setRenderHint(QPainter::Antialiasing, true);
+			QColor borderColor = palette().color(QPalette::WindowText);
+			borderColor.setAlphaF(0.4);
+			painter.setPen(borderColor);
+			painter.setBrush(palette().brush(QPalette::Window));
+			painter.drawRoundedRect(QRectF(rect()).adjusted(0.5, 0.5, -0.5, -0.5), 3.0, 3.0);
+		}
+		QLabel::paintEvent(event);
+	}
+};
+
 QDataStream &operator<< ( QDataStream & ds, const CanvasViewMode & vm )
 {
 	ds << vm.scale
@@ -92,6 +121,17 @@
 	m_buffer = QPixmap();
 	m_bufferRect = QRect();
 	m_renderMode = RENDER_NORMAL;
+
+	m_hudLabel = new CanvasHUDLabel(this);
+	m_hudLabel->setAttribute(Qt::WA_TransparentForMouseEvents, true);
+	m_hudLabel->setFocusPolicy(Qt::NoFocus);
+	m_hudLabel->setContentsMargins(6, 4, 6, 4);
+	updateHUDStyle();
+	m_hudLabel->hide();
+
+	m_hudHideTimer = new QTimer(this);
+	m_hudHideTimer->setSingleShot(true);
+	connect(m_hudHideTimer, &QTimer::timeout, this, &Canvas::hideHUD);
 }
 
 void Canvas::setPreviewVisual(int mode)
@@ -2240,10 +2280,111 @@
 	painter.drawPixmap(x, y, pixmap, sx, sy, sw, sh);
 }
 
+void Canvas::displayHUD(QPointF globalPos, const QString& text, bool autoHide)
+{
+	m_hudLabel->setPixmap(QPixmap());
+	m_hudLabel->setText(text);
+	positionHUD(globalPos, autoHide);
+}
+
+void Canvas::displayHUD(QPointF globalPos, const QPixmap& pixmap, bool autoHide)
+{
+	m_hudLabel->clear();
+	m_hudLabel->setPixmap(pixmap);
+	positionHUD(globalPos, autoHide);
+}
+
+void Canvas::beginHUDLinger(QPointF globalPos)
+{
+	if (!m_hudAutoHide || !m_hudLabel->isVisible())
+		return;
+
+	m_hudLingering = true;
+	m_hudLingerOrigin = globalPos;
+	m_hudHideTimer->start(HUD_HIDE_DELAY_MS);
+}
+
+void Canvas::handleHUDMouseMove(QPointF globalPos)
+{
+	if (!m_hudLingering || !m_hudLabel->isVisible())
+		return;
+
+	const QPointF delta = globalPos - m_hudLingerOrigin;
+	if (delta.x() * delta.x() + delta.y() * delta.y() > HUD_MOVE_GRACE_PX * HUD_MOVE_GRACE_PX)
+		hideHUD();
+}
+
+void Canvas::hideHUD()
+{
+	m_hudHideTimer->stop();
+	m_hudLingering = false;
+	m_hudAutoHide = false;
+	m_hudLabel->hide();
+}
+
+void Canvas::positionHUD(QPointF globalPos, bool autoHide)
+{
+	constexpr int hudOffsetX = 12;
+	constexpr int hudOffsetY = 16;
+	constexpr int hudViewportPadding = 5;
+
+	m_hudLabel->adjustSize();
+
+	QWidget* viewport = m_view->viewport();
+	const QRect visibleRect(viewport->mapToGlobal(QPoint(0, 0)), viewport->size());
+	const QRect hudBounds = visibleRect.adjusted(hudViewportPadding, hudViewportPadding, -hudViewportPadding, -hudViewportPadding);
+	const QPoint anchor = globalPos.toPoint();
+	QPoint hudGlobalPos = anchor + QPoint(hudOffsetX, hudOffsetY);
+	if (hudGlobalPos.x() + m_hudLabel->width() > hudBounds.right() + 1)
+		hudGlobalPos.setX(anchor.x() - m_hudLabel->width() - hudOffsetX);
+	if (hudGlobalPos.y() + m_hudLabel->height() > hudBounds.bottom() + 1)
+		hudGlobalPos.setY(anchor.y() - m_hudLabel->height() - hudOffsetY);
+
+	const int maxX = qMax(hudBounds.left(), hudBounds.right() - m_hudLabel->width() + 1);
+	const int maxY = qMax(hudBounds.top(), hudBounds.bottom() - m_hudLabel->height() + 1);
+	hudGlobalPos.setX(qBound(hudBounds.left(), hudGlobalPos.x(), maxX));
+	hudGlobalPos.setY(qBound(hudBounds.top(), hudGlobalPos.y(), maxY));
+	m_hudLabel->move(mapFromGlobal(hudGlobalPos));
+	if (!m_hudLabel->isVisible())
+	{
+		m_hudLabel->show();
+		m_hudLabel->raise();
+	}
+
+	m_hudAutoHide = autoHide;
+	m_hudLingering = false;
+	if (autoHide && !m_viewMode.m_MouseButtonPressed)
+		m_hudHideTimer->start(HUD_HIDE_DELAY_MS);
+	else
+		m_hudHideTimer->stop();
+}
+
+void Canvas::updateHUDStyle()
+{
+	if (!m_hudLabel)
+		return;
+
+	QPalette hudPalette = QToolTip::palette();
+	hudPalette.setBrush(QPalette::Window, hudPalette.brush(QPalette::ToolTipBase));
+	hudPalette.setBrush(QPalette::WindowText, hudPalette.brush(QPalette::ToolTipText));
+	m_hudLabel->setPalette(hudPalette);
+	m_hudLabel->setFont(QToolTip::font());
+}
+
+void Canvas::changeEvent(QEvent* event)
+{
+	QWidget::changeEvent(event);
+	if (event->type() == QEvent::PaletteChange || event->type() == QEvent::FontChange || event->type() == QEvent::StyleChange)
+		updateHUDStyle();
+}
+
 void Canvas::displayXYHUD(QPointF m)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
+	}
 	double gx, gy, gh, gw, r;
 	if (m_doc->m_Selection->isMultipleSelection())
 	{
@@ -2288,7 +2429,10 @@
 void Canvas::displayCorrectedXYHUD(QPointF m, double x, double y)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
+	}
 	double gx = x;
 	double gy = y;
 	if (m_doc->guidesPrefs().rulerMode)
@@ -2299,81 +2443,91 @@
 	gx -= m_doc->rulerXoffset;
 	gy -= m_doc->rulerYoffset;
 
-	QPointF tooltipPos = m + QPointF(5.0, 5.0);
-	QToolTip::showText(tooltipPos.toPoint(), tr("X: %1\nY: %2").arg(value2String(gx, m_doc->unitIndex(), true, true), value2String(gy, m_doc->unitIndex(), true, true)), this);
+	displayHUD(m, tr("X: %1\nY: %2").arg(value2String(gx, m_doc->unitIndex(), true, true), value2String(gy, m_doc->unitIndex(), true, true)));
 }
 
 void Canvas::displayCorrectedSingleHUD(QPointF m, double val, bool isX)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
+	}
 	double gx = val;
 	if (isX)
 	{
 		if (m_doc->guidesPrefs().rulerMode)
 			gx -= m_doc->currentPage()->xOffset();
 		gx -= m_doc->rulerXoffset;
-		QPointF tooltipPos = m + QPointF(5.0, 5.0);
-		QToolTip::showText(tooltipPos.toPoint(), tr("X: %1").arg(value2String(gx, m_doc->unitIndex(), true, true)), this);
+		displayHUD(m, tr("X: %1").arg(value2String(gx, m_doc->unitIndex(), true, true)));
 	}
 	else
 	{
 		if (m_doc->guidesPrefs().rulerMode)
 			gx -= m_doc->currentPage()->yOffset();
 		gx -= m_doc->rulerYoffset;
-		QPointF tooltipPos = m + QPointF(5.0, 5.0);
-		QToolTip::showText(tooltipPos.toPoint(), tr("Y: %1").arg(value2String(gx, m_doc->unitIndex(), true, true)), this);
+		displayHUD(m, tr("Y: %1").arg(value2String(gx, m_doc->unitIndex(), true, true)));
 	}
 }
 
 void Canvas::displayXYHUD(QPointF m, double x, double y)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
+	}
 
-	QPointF tooltipPos = m + QPointF(5.0, 5.0);
-	QToolTip::showText(tooltipPos.toPoint(), tr("X: %1\nY: %2").arg(value2String(x, m_doc->unitIndex(), true, true), value2String(y, m_doc->unitIndex(), true, true)), this);
+	displayHUD(m, tr("X: %1\nY: %2").arg(value2String(x, m_doc->unitIndex(), true, true), value2String(y, m_doc->unitIndex(), true, true)));
 }
 
 void Canvas::displaySizeHUD(QPointF m, double x, double y, bool isLine)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
+	}
 
-	QPointF tooltipPos = m + QPointF(5.0, 5.0);
 	if (isLine)
-		QToolTip::showText(tooltipPos.toPoint(), tr("Length: %1\nAngle: %2").arg(value2String(x, m_doc->unitIndex(), true, true), value2String(y, SC_DEGREES, true, true)), this);
+		displayHUD(m, tr("Length: %1\nAngle: %2").arg(value2String(x, m_doc->unitIndex(), true, true), value2String(y, SC_DEGREES, true, true)));
 	else
-		QToolTip::showText(tooltipPos.toPoint(), tr("Width: %1\nHeight: %2").arg(value2String(x, m_doc->unitIndex(), true, true), value2String(y, m_doc->unitIndex(), true, true)), this);
+		displayHUD(m, tr("Width: %1\nHeight: %2").arg(value2String(x, m_doc->unitIndex(), true, true), value2String(y, m_doc->unitIndex(), true, true)));
 }
 
 void Canvas::displayRotHUD(QPointF m, double rot)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
+	}
 	double r;
 	if (rot < 0.0)
 		r = rot * -1.0;
 	else
 		r = 360.0 - rot;
-	QPointF tooltipPos = m + QPointF(5.0, 5.0);
-	QToolTip::showText(tooltipPos.toPoint(), tr("Angle: %1").arg(value2String(r, SC_DEGREES, true, true)), this);
+	displayHUD(m, tr("Angle: %1").arg(value2String(r, SC_DEGREES, true, true)));
 }
 
 void Canvas::displayRealRotHUD(QPointF m, double rot)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
-	QPointF tooltipPos = m + QPointF(5.0, 5.0);
-	QToolTip::showText(tooltipPos.toPoint(), tr("Angle: %1").arg(value2String(rot, SC_DEGREES, true, true)), this);
+	}
+	displayHUD(m, tr("Angle: %1").arg(value2String(rot, SC_DEGREES, true, true)));
 }
 
 void Canvas::displayDoubleHUD(QPointF point, const QString& label, double value)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
-	QPointF tooltipPos = point + QPointF(5.0, 5.0);
-	QToolTip::showText(tooltipPos.toPoint(), QString("%1: %2").arg(label, value2String(value, m_doc->unitIndex(), true, true)), this);
+	}
+	displayHUD(point, QString("%1: %2").arg(label, value2String(value, m_doc->unitIndex(), true, true)));
 }
 
 void Canvas::setupEditHRuler(PageItem * item, bool forceAndReset)
Index: scribus/canvasmode_imageimport.h
===================================================================
--- scribus/canvasmode_imageimport.h	(revision 27824)
+++ scribus/canvasmode_imageimport.h	(working copy)
@@ -55,12 +55,14 @@
 
 private:
 	bool m_keyRepeat {false};
-	QString m_tipText;
+	QPixmap m_previewPixmap;
+	QString m_previewText;
 	QStringList m_imageList;
 	ScribusMainWindow *m_ScMW {nullptr};
 	double m_Mx {0.0};
 	double m_My {0.0};
-	void newToolTip(const QString& name);
+	void displayPreview(QPointF globalPos);
+	void updatePreview(const QString& name);
 };
 
 #endif // CANVASMODE_IMAGEIMPORT_H
Index: scribus/canvasmode_imageimport.cpp
===================================================================
--- scribus/canvasmode_imageimport.cpp	(revision 27824)
+++ scribus/canvasmode_imageimport.cpp	(working copy)
@@ -16,7 +16,6 @@
 
 
 #include <QApplication>
-#include <QBuffer>
 #include <QCursor>
 #include <QEvent>
 #include <QMouseEvent>
@@ -24,7 +23,6 @@
 #include <QPixmap>
 #include <QPoint>
 #include <QRect>
-#include <QToolTip>
 #include <QDebug>
 
 #include "canvas.h"
@@ -38,6 +36,9 @@
 
 CanvasMode_ImageImport::CanvasMode_ImageImport(ScribusView *view) : CanvasMode(view), m_ScMW(view->m_ScMW)
 {
+	const QPoint cursorPos = QCursor::pos();
+	m_Mx = cursorPos.x();
+	m_My = cursorPos.y();
 }
 
 void CanvasMode_ImageImport::setImageList(QStringList l)
@@ -53,36 +54,48 @@
 			m_view->requestMode(submodePaintingDone);
 		}
 		else
-			newToolTip(l.first());
+			updatePreview(l.first());
 	}
 	else
-		newToolTip(l.first());
+		updatePreview(l.first());
 }
 
-void CanvasMode_ImageImport::newToolTip(const QString& name)
+void CanvasMode_ImageImport::displayPreview(QPointF globalPos)
 {
-	QImage pm(80, 80, QImage::Format_ARGB32_Premultiplied);
-	QBrush b(QColor(205,205,205), IconManager::instance().loadPixmap("testfill"));
-	
-	QPainter p;
-	p.begin(&pm);
-	p.fillRect(QRectF(0, 0, 80, 80), b);
+	if (!m_previewPixmap.isNull())
+		m_canvas->displayHUD(globalPos, m_previewPixmap, false);
+	else
+		m_canvas->displayHUD(globalPos, m_previewText, false);
+}
+
+void CanvasMode_ImageImport::updatePreview(const QString& name)
+{
+	QPixmap preview(80, 80);
+	QBrush background(QColor(205, 205, 205), IconManager::instance().loadPixmap("testfill"));
 	QImage thumb;
-	if (thumb.load(name))
+	const bool hasThumbnail = thumb.load(name);
+	{
+		QPainter painter(&preview);
+		painter.fillRect(QRectF(0, 0, 80, 80), background);
+		if (hasThumbnail)
+		{
+			thumb = thumb.scaled(80, 80, Qt::KeepAspectRatio);
+			painter.drawImage((80 - thumb.width()) / 2, (80 - thumb.height()) / 2, thumb);
+		}
+	}
+
+	if (hasThumbnail)
 	{
-		thumb = thumb.scaled(80,80,Qt::KeepAspectRatio);
-		p.drawImage((80 - thumb.width()) / 2, (80 - thumb.height()) / 2, thumb);
-		QBuffer buffer;
-		buffer.open(QIODevice::WriteOnly);
-		pm.save(&buffer, "PNG");
-		QByteArray ba = buffer.buffer().toBase64();
-		buffer.close();
-		m_tipText = "<p align=\"center\"><img src=\"data:image/png;base64," + QString(ba) + "\"></p>";
+		m_previewPixmap = preview;
+		m_previewText.clear();
 	}
 	else
-		m_tipText = "<p align=\"center\">" + name.right(name.lastIndexOf("/")) + "</p>";
-	QToolTip::showText(QPoint(m_Mx,m_My), m_tipText, qApp->activeWindow());
-	p.end();
+	{
+		m_previewPixmap = QPixmap();
+		m_previewText = name.right(name.lastIndexOf("/"));
+	}
+
+	displayPreview(QPointF(m_Mx, m_My));
 }
 
 void CanvasMode_ImageImport::drawControls(QPainter* p)
@@ -125,6 +138,7 @@
 
 void CanvasMode_ImageImport::leaveEvent(QEvent *e)
 {
+	m_canvas->hideHUD();
 }
 
 void CanvasMode_ImageImport::activate(bool fromGesture)
@@ -146,6 +160,7 @@
 
 void CanvasMode_ImageImport::deactivate(bool forGesture)
 {
+	m_canvas->hideHUD();
 	m_view->setRedrawMarkerShown(false);
 	CanvasMode::deactivate(forGesture);
 }
@@ -161,8 +176,7 @@
 {
 	m_Mx = m->globalPosition().x();
 	m_My = m->globalPosition().y();
-	QToolTip::showText(m->globalPosition().toPoint(), m_tipText, qApp->activeWindow());
-	QToolTip::showText(m->globalPosition().toPoint(), m_tipText + "<b></b>", qApp->activeWindow());
+	displayPreview(m->globalPosition());
 	m->accept();
 	PageItem *item;
 	if ((item = m_canvas->itemUnderCursor(m->globalPosition())) != nullptr)
@@ -249,8 +263,8 @@
 	if (m_imageList.isEmpty())
 	{
 		m_view->requestMode(submodePaintingDone);
-		QToolTip::hideText();
+		m_canvas->hideHUD();
 	}
 	else
-		newToolTip(m_imageList.constFirst());
+		updatePreview(m_imageList.constFirst());
 }
Index: scribus/scribusview.cpp
===================================================================
--- scribus/scribusview.cpp	(revision 27824)
+++ scribus/scribusview.cpp	(working copy)
@@ -3369,6 +3369,7 @@
 		emit MousePos(p.x(),p.y());
 		horizRuler->draw(m->position().x() + qRound(m_doc->minCanvasCoordinate.x() * m_canvas->scale()));
 		vertRuler->draw(m->position().y() + qRound(m_doc->minCanvasCoordinate.y() * m_canvas->scale()));
+		m_canvas->handleHUDMouseMove(m->globalPosition());
 		m_canvasMode->mouseMoveEvent(m);
 		return true;
 	}
@@ -3377,6 +3378,7 @@
 		auto* m = dynamic_cast<QMouseEvent*> (event);
 		m_canvasMode->mouseReleaseEvent(m);
 		m_canvas->m_viewMode.m_MouseButtonPressed = false;
+		m_canvas->beginHUDLinger(m->globalPosition());
 		if (linkAfterDraw)
 		{
 			//user creates new frame using linking tool
canvas-hud-drag-fix-v1.06.patch (17,222 bytes)   
HUD, border.png (12,685 bytes)   
HUD, border.png (12,685 bytes)   

buttar

2026-09-08 13:40

reporter   ~0054439

I kept experimenting and some more changes made sense moved here. | canvas-hud-drag-fix-v1.08.patch

* Change from v1.06
  -- Reparents the shared HUD widget from `Canvas` to the `ScribusView` viewport.
  -- Converts final HUD placement to viewport coordinates instead of canvas-content coordinates.

* Reason
  -- The HUD is screen-space interaction feedback, while `Canvas` is scrollable/zoomable document content.
  -- Keeping the HUD under `Canvas` allowed zooming or scrolling to move a stationary HUD away from the pointer until the next mouse move.

* Result
  -- HUDs remain pointer/screen anchored while the document zooms or scrolls underneath.
  -- The fix applies centrally to all shared HUD users, including create/move/resize/rotate and Image Import.
  -- No zoom-specific hooks or per-mode workaround required.
  -- Styling, timing, border, edge handling and other v1.06 behaviour are unchanged.
canvas-hud-drag-fix-v1.08.patch (17,246 bytes)   
Index: scribus/canvas.h
===================================================================
--- scribus/canvas.h	(revision 27824)
+++ scribus/canvas.h	(working copy)
@@ -39,6 +39,9 @@
 #include "pageitempointer.h"
 
 
+class QLabel;
+class QTimer;
+
 class ScPage;
 class PageItem;
 class ScLayer;
@@ -208,11 +211,16 @@
 	void displayRotHUD(QPointF m, double rot);
 	void displayRealRotHUD(QPointF m, double rot);
 	/**
-	 * Displays a tooltip of the format <code>{label}: {N} {unit}</code> where <code>N</code> is
+	 * Displays a HUD of the format <code>{label}: {N} {unit}</code> where <code>N</code> is
 	 * @a value converted to the current document unit and <code>unit</code> the current document
-	 * unit. The tooltip will be displayed nearby the global point @a point.
+	 * unit. The HUD will be displayed nearby the global point @a point.
 	 */
 	void displayDoubleHUD(QPointF point, const QString& label, double value);
+	void displayHUD(QPointF globalPos, const QString& text, bool autoHide = true);
+	void displayHUD(QPointF globalPos, const QPixmap& pixmap, bool autoHide = true);
+	void beginHUDLinger(QPointF globalPos);
+	void handleHUDMouseMove(QPointF globalPos);
+	void hideHUD();
 
 	void setupEditHRuler(PageItem * item, bool forceAndReset = false);
 	
@@ -263,6 +271,9 @@
 	QPixmap createPixmap(double w, double h);
 	// draw a potentially hidpi pixmap
 	void drawPixmap(QPainter &painter, double x, double y, const QPixmap &pixmap, double sx, double sy, double sw, double sh);
+	void positionHUD(QPointF globalPos, bool autoHide);
+	void updateHUDStyle();
+	void changeEvent(QEvent* event) override;
 		
 private:
 	ScribusDoc* m_doc;
@@ -275,6 +286,11 @@
 	QPixmap m_selectionBuffer;
 	QRect   m_selectionRect;
 	QPoint  m_oldMinCanvasCoordinate;
+	QLabel* m_hudLabel { nullptr };
+	QTimer* m_hudHideTimer { nullptr };
+	bool m_hudAutoHide { false };
+	bool m_hudLingering { false };
+	QPointF m_hudLingerOrigin;
 };
 
 
Index: scribus/canvas.cpp
===================================================================
--- scribus/canvas.cpp	(revision 27824)
+++ scribus/canvas.cpp	(working copy)
@@ -19,6 +19,11 @@
 #include <cmath>
 
 // #include <QDebug>
+#include <QEvent>
+#include <QLabel>
+#include <QPainter>
+#include <QPalette>
+#include <QTimer>
 #include <QToolTip>
 #include <QWidget>
 
@@ -47,6 +52,30 @@
 	return p;
 }
 
+constexpr int HUD_HIDE_DELAY_MS = 150;
+constexpr double HUD_MOVE_GRACE_PX = 5.0;
+
+class CanvasHUDLabel final : public QLabel
+{
+public:
+	using QLabel::QLabel;
+
+protected:
+	void paintEvent(QPaintEvent* event) override
+	{
+		{
+			QPainter painter(this);
+			painter.setRenderHint(QPainter::Antialiasing, true);
+			QColor borderColor = palette().color(QPalette::WindowText);
+			borderColor.setAlphaF(0.4);
+			painter.setPen(borderColor);
+			painter.setBrush(palette().brush(QPalette::Window));
+			painter.drawRoundedRect(QRectF(rect()).adjusted(0.5, 0.5, -0.5, -0.5), 3.0, 3.0);
+		}
+		QLabel::paintEvent(event);
+	}
+};
+
 QDataStream &operator<< ( QDataStream & ds, const CanvasViewMode & vm )
 {
 	ds << vm.scale
@@ -92,6 +121,17 @@
 	m_buffer = QPixmap();
 	m_bufferRect = QRect();
 	m_renderMode = RENDER_NORMAL;
+
+	m_hudLabel = new CanvasHUDLabel(m_view->viewport());
+	m_hudLabel->setAttribute(Qt::WA_TransparentForMouseEvents, true);
+	m_hudLabel->setFocusPolicy(Qt::NoFocus);
+	m_hudLabel->setContentsMargins(6, 4, 6, 4);
+	updateHUDStyle();
+	m_hudLabel->hide();
+
+	m_hudHideTimer = new QTimer(this);
+	m_hudHideTimer->setSingleShot(true);
+	connect(m_hudHideTimer, &QTimer::timeout, this, &Canvas::hideHUD);
 }
 
 void Canvas::setPreviewVisual(int mode)
@@ -2240,10 +2280,111 @@
 	painter.drawPixmap(x, y, pixmap, sx, sy, sw, sh);
 }
 
+void Canvas::displayHUD(QPointF globalPos, const QString& text, bool autoHide)
+{
+	m_hudLabel->setPixmap(QPixmap());
+	m_hudLabel->setText(text);
+	positionHUD(globalPos, autoHide);
+}
+
+void Canvas::displayHUD(QPointF globalPos, const QPixmap& pixmap, bool autoHide)
+{
+	m_hudLabel->clear();
+	m_hudLabel->setPixmap(pixmap);
+	positionHUD(globalPos, autoHide);
+}
+
+void Canvas::beginHUDLinger(QPointF globalPos)
+{
+	if (!m_hudAutoHide || !m_hudLabel->isVisible())
+		return;
+
+	m_hudLingering = true;
+	m_hudLingerOrigin = globalPos;
+	m_hudHideTimer->start(HUD_HIDE_DELAY_MS);
+}
+
+void Canvas::handleHUDMouseMove(QPointF globalPos)
+{
+	if (!m_hudLingering || !m_hudLabel->isVisible())
+		return;
+
+	const QPointF delta = globalPos - m_hudLingerOrigin;
+	if (delta.x() * delta.x() + delta.y() * delta.y() > HUD_MOVE_GRACE_PX * HUD_MOVE_GRACE_PX)
+		hideHUD();
+}
+
+void Canvas::hideHUD()
+{
+	m_hudHideTimer->stop();
+	m_hudLingering = false;
+	m_hudAutoHide = false;
+	m_hudLabel->hide();
+}
+
+void Canvas::positionHUD(QPointF globalPos, bool autoHide)
+{
+	constexpr int hudOffsetX = 12;
+	constexpr int hudOffsetY = 16;
+	constexpr int hudViewportPadding = 5;
+
+	m_hudLabel->adjustSize();
+
+	QWidget* viewport = m_view->viewport();
+	const QRect visibleRect(viewport->mapToGlobal(QPoint(0, 0)), viewport->size());
+	const QRect hudBounds = visibleRect.adjusted(hudViewportPadding, hudViewportPadding, -hudViewportPadding, -hudViewportPadding);
+	const QPoint anchor = globalPos.toPoint();
+	QPoint hudGlobalPos = anchor + QPoint(hudOffsetX, hudOffsetY);
+	if (hudGlobalPos.x() + m_hudLabel->width() > hudBounds.right() + 1)
+		hudGlobalPos.setX(anchor.x() - m_hudLabel->width() - hudOffsetX);
+	if (hudGlobalPos.y() + m_hudLabel->height() > hudBounds.bottom() + 1)
+		hudGlobalPos.setY(anchor.y() - m_hudLabel->height() - hudOffsetY);
+
+	const int maxX = qMax(hudBounds.left(), hudBounds.right() - m_hudLabel->width() + 1);
+	const int maxY = qMax(hudBounds.top(), hudBounds.bottom() - m_hudLabel->height() + 1);
+	hudGlobalPos.setX(qBound(hudBounds.left(), hudGlobalPos.x(), maxX));
+	hudGlobalPos.setY(qBound(hudBounds.top(), hudGlobalPos.y(), maxY));
+	m_hudLabel->move(viewport->mapFromGlobal(hudGlobalPos));
+	if (!m_hudLabel->isVisible())
+	{
+		m_hudLabel->show();
+		m_hudLabel->raise();
+	}
+
+	m_hudAutoHide = autoHide;
+	m_hudLingering = false;
+	if (autoHide && !m_viewMode.m_MouseButtonPressed)
+		m_hudHideTimer->start(HUD_HIDE_DELAY_MS);
+	else
+		m_hudHideTimer->stop();
+}
+
+void Canvas::updateHUDStyle()
+{
+	if (!m_hudLabel)
+		return;
+
+	QPalette hudPalette = QToolTip::palette();
+	hudPalette.setBrush(QPalette::Window, hudPalette.brush(QPalette::ToolTipBase));
+	hudPalette.setBrush(QPalette::WindowText, hudPalette.brush(QPalette::ToolTipText));
+	m_hudLabel->setPalette(hudPalette);
+	m_hudLabel->setFont(QToolTip::font());
+}
+
+void Canvas::changeEvent(QEvent* event)
+{
+	QWidget::changeEvent(event);
+	if (event->type() == QEvent::PaletteChange || event->type() == QEvent::FontChange || event->type() == QEvent::StyleChange)
+		updateHUDStyle();
+}
+
 void Canvas::displayXYHUD(QPointF m)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
+	}
 	double gx, gy, gh, gw, r;
 	if (m_doc->m_Selection->isMultipleSelection())
 	{
@@ -2288,7 +2429,10 @@
 void Canvas::displayCorrectedXYHUD(QPointF m, double x, double y)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
+	}
 	double gx = x;
 	double gy = y;
 	if (m_doc->guidesPrefs().rulerMode)
@@ -2299,81 +2443,91 @@
 	gx -= m_doc->rulerXoffset;
 	gy -= m_doc->rulerYoffset;
 
-	QPointF tooltipPos = m + QPointF(5.0, 5.0);
-	QToolTip::showText(tooltipPos.toPoint(), tr("X: %1\nY: %2").arg(value2String(gx, m_doc->unitIndex(), true, true), value2String(gy, m_doc->unitIndex(), true, true)), this);
+	displayHUD(m, tr("X: %1\nY: %2").arg(value2String(gx, m_doc->unitIndex(), true, true), value2String(gy, m_doc->unitIndex(), true, true)));
 }
 
 void Canvas::displayCorrectedSingleHUD(QPointF m, double val, bool isX)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
+	}
 	double gx = val;
 	if (isX)
 	{
 		if (m_doc->guidesPrefs().rulerMode)
 			gx -= m_doc->currentPage()->xOffset();
 		gx -= m_doc->rulerXoffset;
-		QPointF tooltipPos = m + QPointF(5.0, 5.0);
-		QToolTip::showText(tooltipPos.toPoint(), tr("X: %1").arg(value2String(gx, m_doc->unitIndex(), true, true)), this);
+		displayHUD(m, tr("X: %1").arg(value2String(gx, m_doc->unitIndex(), true, true)));
 	}
 	else
 	{
 		if (m_doc->guidesPrefs().rulerMode)
 			gx -= m_doc->currentPage()->yOffset();
 		gx -= m_doc->rulerYoffset;
-		QPointF tooltipPos = m + QPointF(5.0, 5.0);
-		QToolTip::showText(tooltipPos.toPoint(), tr("Y: %1").arg(value2String(gx, m_doc->unitIndex(), true, true)), this);
+		displayHUD(m, tr("Y: %1").arg(value2String(gx, m_doc->unitIndex(), true, true)));
 	}
 }
 
 void Canvas::displayXYHUD(QPointF m, double x, double y)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
+	}
 
-	QPointF tooltipPos = m + QPointF(5.0, 5.0);
-	QToolTip::showText(tooltipPos.toPoint(), tr("X: %1\nY: %2").arg(value2String(x, m_doc->unitIndex(), true, true), value2String(y, m_doc->unitIndex(), true, true)), this);
+	displayHUD(m, tr("X: %1\nY: %2").arg(value2String(x, m_doc->unitIndex(), true, true), value2String(y, m_doc->unitIndex(), true, true)));
 }
 
 void Canvas::displaySizeHUD(QPointF m, double x, double y, bool isLine)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
+	}
 
-	QPointF tooltipPos = m + QPointF(5.0, 5.0);
 	if (isLine)
-		QToolTip::showText(tooltipPos.toPoint(), tr("Length: %1\nAngle: %2").arg(value2String(x, m_doc->unitIndex(), true, true), value2String(y, SC_DEGREES, true, true)), this);
+		displayHUD(m, tr("Length: %1\nAngle: %2").arg(value2String(x, m_doc->unitIndex(), true, true), value2String(y, SC_DEGREES, true, true)));
 	else
-		QToolTip::showText(tooltipPos.toPoint(), tr("Width: %1\nHeight: %2").arg(value2String(x, m_doc->unitIndex(), true, true), value2String(y, m_doc->unitIndex(), true, true)), this);
+		displayHUD(m, tr("Width: %1\nHeight: %2").arg(value2String(x, m_doc->unitIndex(), true, true), value2String(y, m_doc->unitIndex(), true, true)));
 }
 
 void Canvas::displayRotHUD(QPointF m, double rot)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
+	}
 	double r;
 	if (rot < 0.0)
 		r = rot * -1.0;
 	else
 		r = 360.0 - rot;
-	QPointF tooltipPos = m + QPointF(5.0, 5.0);
-	QToolTip::showText(tooltipPos.toPoint(), tr("Angle: %1").arg(value2String(r, SC_DEGREES, true, true)), this);
+	displayHUD(m, tr("Angle: %1").arg(value2String(r, SC_DEGREES, true, true)));
 }
 
 void Canvas::displayRealRotHUD(QPointF m, double rot)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
-	QPointF tooltipPos = m + QPointF(5.0, 5.0);
-	QToolTip::showText(tooltipPos.toPoint(), tr("Angle: %1").arg(value2String(rot, SC_DEGREES, true, true)), this);
+	}
+	displayHUD(m, tr("Angle: %1").arg(value2String(rot, SC_DEGREES, true, true)));
 }
 
 void Canvas::displayDoubleHUD(QPointF point, const QString& label, double value)
 {
 	if (!PrefsManager::instance().appPrefs.displayPrefs.showMouseCoordinates)
+	{
+		hideHUD();
 		return;
-	QPointF tooltipPos = point + QPointF(5.0, 5.0);
-	QToolTip::showText(tooltipPos.toPoint(), QString("%1: %2").arg(label, value2String(value, m_doc->unitIndex(), true, true)), this);
+	}
+	displayHUD(point, QString("%1: %2").arg(label, value2String(value, m_doc->unitIndex(), true, true)));
 }
 
 void Canvas::setupEditHRuler(PageItem * item, bool forceAndReset)
Index: scribus/canvasmode_imageimport.h
===================================================================
--- scribus/canvasmode_imageimport.h	(revision 27824)
+++ scribus/canvasmode_imageimport.h	(working copy)
@@ -55,12 +55,14 @@
 
 private:
 	bool m_keyRepeat {false};
-	QString m_tipText;
+	QPixmap m_previewPixmap;
+	QString m_previewText;
 	QStringList m_imageList;
 	ScribusMainWindow *m_ScMW {nullptr};
 	double m_Mx {0.0};
 	double m_My {0.0};
-	void newToolTip(const QString& name);
+	void displayPreview(QPointF globalPos);
+	void updatePreview(const QString& name);
 };
 
 #endif // CANVASMODE_IMAGEIMPORT_H
Index: scribus/canvasmode_imageimport.cpp
===================================================================
--- scribus/canvasmode_imageimport.cpp	(revision 27824)
+++ scribus/canvasmode_imageimport.cpp	(working copy)
@@ -16,7 +16,6 @@
 
 
 #include <QApplication>
-#include <QBuffer>
 #include <QCursor>
 #include <QEvent>
 #include <QMouseEvent>
@@ -24,7 +23,6 @@
 #include <QPixmap>
 #include <QPoint>
 #include <QRect>
-#include <QToolTip>
 #include <QDebug>
 
 #include "canvas.h"
@@ -38,6 +36,9 @@
 
 CanvasMode_ImageImport::CanvasMode_ImageImport(ScribusView *view) : CanvasMode(view), m_ScMW(view->m_ScMW)
 {
+	const QPoint cursorPos = QCursor::pos();
+	m_Mx = cursorPos.x();
+	m_My = cursorPos.y();
 }
 
 void CanvasMode_ImageImport::setImageList(QStringList l)
@@ -53,36 +54,48 @@
 			m_view->requestMode(submodePaintingDone);
 		}
 		else
-			newToolTip(l.first());
+			updatePreview(l.first());
 	}
 	else
-		newToolTip(l.first());
+		updatePreview(l.first());
 }
 
-void CanvasMode_ImageImport::newToolTip(const QString& name)
+void CanvasMode_ImageImport::displayPreview(QPointF globalPos)
 {
-	QImage pm(80, 80, QImage::Format_ARGB32_Premultiplied);
-	QBrush b(QColor(205,205,205), IconManager::instance().loadPixmap("testfill"));
-	
-	QPainter p;
-	p.begin(&pm);
-	p.fillRect(QRectF(0, 0, 80, 80), b);
+	if (!m_previewPixmap.isNull())
+		m_canvas->displayHUD(globalPos, m_previewPixmap, false);
+	else
+		m_canvas->displayHUD(globalPos, m_previewText, false);
+}
+
+void CanvasMode_ImageImport::updatePreview(const QString& name)
+{
+	QPixmap preview(80, 80);
+	QBrush background(QColor(205, 205, 205), IconManager::instance().loadPixmap("testfill"));
 	QImage thumb;
-	if (thumb.load(name))
+	const bool hasThumbnail = thumb.load(name);
+	{
+		QPainter painter(&preview);
+		painter.fillRect(QRectF(0, 0, 80, 80), background);
+		if (hasThumbnail)
+		{
+			thumb = thumb.scaled(80, 80, Qt::KeepAspectRatio);
+			painter.drawImage((80 - thumb.width()) / 2, (80 - thumb.height()) / 2, thumb);
+		}
+	}
+
+	if (hasThumbnail)
 	{
-		thumb = thumb.scaled(80,80,Qt::KeepAspectRatio);
-		p.drawImage((80 - thumb.width()) / 2, (80 - thumb.height()) / 2, thumb);
-		QBuffer buffer;
-		buffer.open(QIODevice::WriteOnly);
-		pm.save(&buffer, "PNG");
-		QByteArray ba = buffer.buffer().toBase64();
-		buffer.close();
-		m_tipText = "<p align=\"center\"><img src=\"data:image/png;base64," + QString(ba) + "\"></p>";
+		m_previewPixmap = preview;
+		m_previewText.clear();
 	}
 	else
-		m_tipText = "<p align=\"center\">" + name.right(name.lastIndexOf("/")) + "</p>";
-	QToolTip::showText(QPoint(m_Mx,m_My), m_tipText, qApp->activeWindow());
-	p.end();
+	{
+		m_previewPixmap = QPixmap();
+		m_previewText = name.right(name.lastIndexOf("/"));
+	}
+
+	displayPreview(QPointF(m_Mx, m_My));
 }
 
 void CanvasMode_ImageImport::drawControls(QPainter* p)
@@ -125,6 +138,7 @@
 
 void CanvasMode_ImageImport::leaveEvent(QEvent *e)
 {
+	m_canvas->hideHUD();
 }
 
 void CanvasMode_ImageImport::activate(bool fromGesture)
@@ -146,6 +160,7 @@
 
 void CanvasMode_ImageImport::deactivate(bool forGesture)
 {
+	m_canvas->hideHUD();
 	m_view->setRedrawMarkerShown(false);
 	CanvasMode::deactivate(forGesture);
 }
@@ -161,8 +176,7 @@
 {
 	m_Mx = m->globalPosition().x();
 	m_My = m->globalPosition().y();
-	QToolTip::showText(m->globalPosition().toPoint(), m_tipText, qApp->activeWindow());
-	QToolTip::showText(m->globalPosition().toPoint(), m_tipText + "<b></b>", qApp->activeWindow());
+	displayPreview(m->globalPosition());
 	m->accept();
 	PageItem *item;
 	if ((item = m_canvas->itemUnderCursor(m->globalPosition())) != nullptr)
@@ -249,8 +263,8 @@
 	if (m_imageList.isEmpty())
 	{
 		m_view->requestMode(submodePaintingDone);
-		QToolTip::hideText();
+		m_canvas->hideHUD();
 	}
 	else
-		newToolTip(m_imageList.constFirst());
+		updatePreview(m_imageList.constFirst());
 }
Index: scribus/scribusview.cpp
===================================================================
--- scribus/scribusview.cpp	(revision 27824)
+++ scribus/scribusview.cpp	(working copy)
@@ -3369,6 +3369,7 @@
 		emit MousePos(p.x(),p.y());
 		horizRuler->draw(m->position().x() + qRound(m_doc->minCanvasCoordinate.x() * m_canvas->scale()));
 		vertRuler->draw(m->position().y() + qRound(m_doc->minCanvasCoordinate.y() * m_canvas->scale()));
+		m_canvas->handleHUDMouseMove(m->globalPosition());
 		m_canvasMode->mouseMoveEvent(m);
 		return true;
 	}
@@ -3377,6 +3378,7 @@
 		auto* m = dynamic_cast<QMouseEvent*> (event);
 		m_canvasMode->mouseReleaseEvent(m);
 		m_canvas->m_viewMode.m_MouseButtonPressed = false;
+		m_canvas->beginHUDLinger(m->globalPosition());
 		if (linkAfterDraw)
 		{
 			//user creates new frame using linking tool
canvas-hud-drag-fix-v1.08.patch (17,246 bytes)   

Issue History

Date Modified Username Field Change
2026-07-03 12:51 buttar New Issue
2026-07-03 12:51 buttar File Added: cannot_draw_tables_fast.mp4
2026-07-03 21:07 cbradney Note Added: 0053939
2026-07-04 05:32 buttar Note Added: 0053943
2026-07-04 07:25 ale Note Added: 0053949
2026-07-04 07:26 ale Tag Attached: #cant reproduce
2026-07-04 08:00 buttar Note Added: 0053954
2026-07-04 08:00 buttar File Added: quick_mouse_movement_1.7.2flatpak.mp4
2026-07-06 04:45 buttar Note Added: 0053983
2026-07-06 05:51 PeterBenedek Note Added: 0053986
2026-09-08 07:52 buttar Note Added: 0054435
2026-09-08 07:52 buttar File Added: canvas-hud-drag-fix-v1.05.patch
2026-09-08 07:52 buttar Tag Attached: #please_test
2026-09-08 07:54 buttar Note Added: 0054436
2026-09-08 08:10 buttar Note Added: 0054437
2026-09-08 08:10 buttar File Added: new hud (no issue).mp4
2026-09-08 08:10 buttar File Added: old hud (with issue).mp4
2026-09-08 09:25 buttar Note Added: 0054438
2026-09-08 09:25 buttar File Added: canvas-hud-drag-fix-v1.06.patch
2026-09-08 09:25 buttar File Added: HUD, border.png
2026-09-08 13:40 buttar Note Added: 0054439
2026-09-08 13:40 buttar File Added: canvas-hud-drag-fix-v1.08.patch