View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0017407 | Scribus | User Interface | public | 2025-02-06 16:05 | 2025-02-06 16:10 |
Reporter | ale | Assigned To | |||
Priority | normal | Severity | minor | Reproducibility | N/A |
Status | new | Resolution | open | ||
Product Version | 1.7.1.svn | ||||
Summary | 0017407: [PATCH] Limit the width of the tooltips | ||||
Description | Many 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. | ||||
Tags | No tags attached. | ||||
Patch | Yes | ||||
|
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; } |