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;
 }
 
