View Issue Details

IDProjectCategoryView StatusLast Update
0017407ScribusUser Interfacepublic2025-02-06 16:10
Reporterale Assigned To 
PrioritynormalSeverityminorReproducibilityN/A
Status newResolutionopen 
Product Version1.7.1.svn 
Summary0017407: [PATCH] Limit the width of the tooltips
DescriptionMany tooltips are way too long.

Sadly, Qt only word wraps tooltips that are recognized as rich text.

The attached patch, proposes three different ways to word wrap them:

- Modifying all the tooltips and get Qt to recognize them as containing rich text.
- Use a Regex (in the proposed patch, the patter is created on each call of the function, but it can be cached as static class variable; not sure it makes a real difference).
- A loop looking for the next space where the text can be wrapped.

The first one is a bit of a hack (and probably not the fastest one)
The second one is the most elegant in my eyes, but I wonder how performant it is or it can be made.
The third one is hard to read (and I'm not sure it's 100% correct), but probably the fastest one (and it might be possible to even avoid that lines is reallocated when appending to it).
(And on my monitor, breaking at 50 chars is more readable than what Qt does by default...)

Up to you, to choose one of the three (and remove the #ifdefs.
TagsNo tags attached.
PatchYes

Activities

ale

2025-02-06 16:09

manager   ~0052018

Last edited: 2025-02-06 16:10

This patch is inspired by https://stackoverflow.com/questions/4795757/is-there-a-better-way-to-wordwrap-text-in-qtooltip-than-just-using-regexp
tooltip-wordwrap.diff (4,880 bytes)   
From cb856934032884bad4aa861c3f4ae24f8ad0e0d3 Mon Sep 17 00:00:00 2001
From: ale rimoldi <ale@graphicslab.org>
Date: Thu, 6 Feb 2025 17:08:41 +0100
Subject: wordwrape the tooltips


diff --git a/scribus/CMakeLists_Sources.txt b/scribus/CMakeLists_Sources.txt
index 4ba985604..7ddea93b7 100644
--- a/scribus/CMakeLists_Sources.txt
+++ b/scribus/CMakeLists_Sources.txt
@@ -94,6 +94,7 @@ set(SCRIBUS_SOURCES
 	guidesdelegate.cpp
 	guidesmodel.cpp
 	guidesview.cpp
+	guiutil.cpp
 	hyphenator.cpp
 	iconmanager.cpp
 	ioapi.c
diff --git a/scribus/guiutil.cpp b/scribus/guiutil.cpp
new file mode 100644
index 000000000..7a3df083a
--- /dev/null
+++ b/scribus/guiutil.cpp
@@ -0,0 +1,83 @@
+#include "guiutil.h"
+
+// This file currently contains three possible algorithms for wordrapping the tooltips.
+// Please, pick one.
+// #define TOOLTIP_ALGO_REGEX
+#define TOOLTIP_ALGO_FIND
+// #define TOOLTIP_ALGO_RICH_TEXT
+
+#include <QEvent>
+#include <QObject>
+#include <QString>
+#ifdef TOOLTIP_ALGO_RICH_TEXT
+#include <QTextDocument>
+#endif
+#ifdef TOOLTIP_ALGO_REGEX
+#include <QRegularExpression>
+#endif
+#include <QWidget>
+
+namespace GUIUtil
+{
+
+bool ToolTipWithRichTextFilter::eventFilter(QObject* obj, QEvent* event)
+{
+	const int size_threshold = 60;
+#if defined(TOOLTIP_ALGO_REGEX) || defined(TOOLTIP_ALGO_FIND)
+	const int max_chars_per_line = 50;
+#endif
+    if (event->type() == QEvent::ToolTipChange)
+    {
+        QWidget* widget = static_cast<QWidget*>(obj);
+        QString tooltip = widget->toolTip().trimmed();
+#ifdef TOOLTIP_ALGO_REGEX
+		// split the tooltip in lines of maximum max_chars_per_line chars.
+		// (do not split tooltips containing \n: it's an infinite loop)
+        if (tooltip.size() > size_threshold && !tooltip.contains("\n"))
+		{
+			QRegularExpression re(QString("(.{1,%1})(?:\\s|$)").arg(max_chars_per_line));
+            widget->setToolTip(tooltip.replace(re, "\\1\n").trimmed());
+			return true;
+		}
+#elif defined(TOOLTIP_ALGO_FIND)
+		// split the tooltip in lines of maximum max_chars_per_line chars.
+		// (do not split tooltips containing \n: it's an infinite loop)
+        if (tooltip.size() > size_threshold && !tooltip.contains("\n"))
+		{
+			QString lines;
+			int i = 0;
+			while (true)
+			{
+				if (i + max_chars_per_line >= tooltip.size())
+				{
+					lines += tooltip.right(tooltip.size() - i);
+					break;
+				}
+				int j = tooltip.lastIndexOf(" ", i + max_chars_per_line);
+				if (j <= i)
+				{
+					j = i + max_chars_per_line;
+				}
+				lines += tooltip.mid(i, j - i).trimmed() + "\n";
+				i = j + 1;
+			}
+
+            widget->setToolTip(lines.trimmed());
+		}
+#elif defined(TOOLTIP_ALGO_RICH_TEXT)
+		// Force Qt to treat every tooltip as a rich text one, and get them word wrapped
+        if (tooltip.size() > size_threshold && !tooltip.startsWith("<qt") && !Qt::mightBeRichText(tooltip))
+        {
+            // Envelop with <qt></qt> to make sure Qt detects this as rich text
+            // Escape the current message as HTML and replace \n by <br>
+            tooltip = "<qt>" + tooltip.toHtmlEscaped().replace("\n", "<br>\n") + "</qt>";
+            widget->setToolTip(tooltip);
+            return true;
+        }
+#endif
+    }
+    return QObject::eventFilter(obj, event);
+}
+
+#undef TOOLTIP_ALGO
+}
diff --git a/scribus/guiutil.h b/scribus/guiutil.h
new file mode 100644
index 000000000..569e44e77
--- /dev/null
+++ b/scribus/guiutil.h
@@ -0,0 +1,30 @@
+#ifndef SCRIBUS_GUIUTIL_H_
+#define SCRIBUS_GUIUTIL_H_
+
+#include <QObject>
+
+class QEvent;
+
+/**
+ * Collection of classes and functions for general Scribus GUI tasks.
+ */
+namespace GUIUtil
+{
+
+/**
+ * Attach an event filter to the tooltip creation and avoid that tooltips
+ * display is too wide.
+ *
+ * Inspired by https://stackoverflow.com/a/46212292/5239250
+ */
+class ToolTipWithRichTextFilter : public QObject
+{
+	Q_OBJECT
+public:
+	explicit ToolTipWithRichTextFilter(QObject* parent = nullptr): QObject(parent) {}
+protected:
+	bool eventFilter(QObject* obj, QEvent *evt);
+};
+
+}
+#endif
diff --git a/scribus/scribuscore.cpp b/scribus/scribuscore.cpp
index 2e1362a12..432591dcf 100644
--- a/scribus/scribuscore.cpp
+++ b/scribus/scribuscore.cpp
@@ -34,6 +34,7 @@ for which a new license (GPL+exception) is in place.
 #include "colormgmt/sccolormgmtenginefactory.h"
 #include "commonstrings.h"
 #include "filewatcher.h"
+#include "guiutil.h"
 #include "iconmanager.h"
 #include "langmgr.h"
 #include "localemgr.h"
@@ -218,6 +219,9 @@ int ScribusCore::initScribusCore(bool showSplash, bool showFontInfo, bool showPr
 	icm.setMaxCacheEntries(m_prefsManager.appPrefs.imageCachePrefs.maxCacheEntries);
 	icm.setCompressionLevel(m_prefsManager.appPrefs.imageCachePrefs.compressionLevel);
 	icm.initialize();
+
+	ScQApp->installEventFilter(new GUIUtil::ToolTipWithRichTextFilter(ScQApp));
+
 	return 0;
 }
 
tooltip-wordwrap.diff (4,880 bytes)   

Issue History

Date Modified Username Field Change
2025-02-06 16:05 ale New Issue
2025-02-06 16:09 ale Note Added: 0052018
2025-02-06 16:09 ale File Added: tooltip-wordwrap.diff
2025-02-06 16:10 ale Note Edited: 0052018