commit 059ca0f691d567171e7772c1d56a7f96e7867339
Author: Guillaume Munch <[email protected]>
Date:   Sun Jun 5 21:35:35 2016 +0200

    Line breaks in tooltips
    
    * New function formatToolTip(QString):
    
    Format text for display as a ToolTip, breaking at lines of a certain
    width. Note: this function is expensive. Better call it in a delayed manner,
    i.e. not to fill in a model (see for instance the function
    ToolTipFormatter::eventFilter).
    
    * Install a global event filter that formats tooltips on-the-fly
    
    Inspired from
    
https://github.com/bitcoin/bitcoin/pull/1090/commits/3793fa09ff920fc720dfad3738f105d2c9563662
    but much improved.
    
    When is formatToolTip called automatically? Whenever the tooltip is not 
already
    rich text beginning with <html>, and is defined by the following functions:
     * QWidget::setToolTip(),
     * QAbstractItemModel::setData(..., Qt::ToolTipRole),
     * Inset::toolTip() (added in one of the subsequent patches)
    
    In other words, tooltips can use Qt html and the tooltip will still be 
correctly
    broken. Moreover, it is possible to specify an entirely custom tooltip (not
    subject to automatic formatting) by giving it in its entirety, i.e. starting
    with <html>.
---
 src/frontends/qt4/GuiApplication.cpp   |    4 ++
 src/frontends/qt4/Makefile.am          |    2 +
 src/frontends/qt4/ToolTipFormatter.cpp |   68 ++++++++++++++++++++++++++++++++
 src/frontends/qt4/ToolTipFormatter.h   |   37 +++++++++++++++++
 src/frontends/qt4/qt_helpers.cpp       |   36 +++++++++++++++++
 src/frontends/qt4/qt_helpers.h         |   18 ++++++++
 6 files changed, 165 insertions(+), 0 deletions(-)

diff --git a/src/frontends/qt4/GuiApplication.cpp 
b/src/frontends/qt4/GuiApplication.cpp
index 074492b..a8611f7 100644
--- a/src/frontends/qt4/GuiApplication.cpp
+++ b/src/frontends/qt4/GuiApplication.cpp
@@ -14,6 +14,7 @@
 
 #include "GuiApplication.h"
 
+#include "ToolTipFormatter.h"
 #include "ColorCache.h"
 #include "ColorSet.h"
 #include "GuiClipboard.h"
@@ -1081,6 +1082,9 @@ GuiApplication::GuiApplication(int & argc, char ** argv)
        // This is clearly not enough in a time where we use threads for
        // document preview and/or export. 20 should be OK.
        QThreadPool::globalInstance()->setMaxThreadCount(20);
+
+       // make sure tooltips are formatted
+       installEventFilter(new ToolTipFormatter(this));
 }
 
 
diff --git a/src/frontends/qt4/Makefile.am b/src/frontends/qt4/Makefile.am
index 103105e..190e888 100644
--- a/src/frontends/qt4/Makefile.am
+++ b/src/frontends/qt4/Makefile.am
@@ -153,6 +153,7 @@ SOURCEFILES = \
        TocModel.cpp \
        TocWidget.cpp \
        Toolbars.cpp \
+       ToolTipFormatter.cpp \
        Validator.cpp 
 
 NOMOCHEADER = \
@@ -259,6 +260,7 @@ MOCHEADER = \
        PanelStack.h \
        TocModel.h \
        TocWidget.h \
+       ToolTipFormatter.h \
        Validator.h
 
 UIFILES = \
diff --git a/src/frontends/qt4/ToolTipFormatter.cpp 
b/src/frontends/qt4/ToolTipFormatter.cpp
new file mode 100644
index 0000000..d29012c
--- /dev/null
+++ b/src/frontends/qt4/ToolTipFormatter.cpp
@@ -0,0 +1,68 @@
+/**
+ * \file ToolTipFormatter.cpp
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author Guillaume Munch
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#include <config.h>
+
+#include "ToolTipFormatter.h"
+#include "qt_helpers.h"
+
+#include <QAbstractItemView>
+#include <QTextDocument>
+#include <QTextLayout>
+#include <QToolTip>
+
+
+//#include "support/debug.h"
+//#include "support/lstrings.h"
+
+
+namespace lyx {
+namespace frontend {
+
+
+ToolTipFormatter::ToolTipFormatter(QObject * parent) : QObject(parent) {}
+
+
+bool ToolTipFormatter::eventFilter(QObject * o, QEvent * e)
+{
+       if (e->type() != QEvent::ToolTip)
+               return false;
+
+       // Format the tooltip of the widget being considered.
+       QWidget * w = qobject_cast<QWidget *>(o);
+       if (!w)
+               return false;
+       // Unchanged if empty or already formatted
+       w->setToolTip(formatToolTip(w->toolTip()));
+
+       // Now, if the tooltip is for an item in a QListView or a QTreeView,
+       // then the widget above was probably not the one with the tooltip.
+       // Check if the parent is a QAbstractItemView.
+       QAbstractItemView * iv = qobject_cast<QAbstractItemView *>(w->parent());
+       if (!iv)
+               return false;
+       // In this case, the item is retrieved from the position of the 
QHelpEvent
+       // on the screen.
+       QPoint pos = static_cast<QHelpEvent *>(e)->pos();
+       QModelIndex item = iv->indexAt(pos);
+       QVariant data = iv->model()->data(item, Qt::ToolTipRole);
+       if (data.isValid() && data.typeName() == toqstr("QString"))
+               // Unchanged if empty or already formatted
+               iv->model()->setData(item, formatToolTip(data.toString()),
+                                    Qt::ToolTipRole);
+       // We must let the tooltip event reach its destination.
+       return false;
+}
+
+
+} // namespace frontend
+} // namespace lyx
+
+#include "moc_ToolTipFormatter.cpp"
diff --git a/src/frontends/qt4/ToolTipFormatter.h 
b/src/frontends/qt4/ToolTipFormatter.h
new file mode 100644
index 0000000..7b943ff
--- /dev/null
+++ b/src/frontends/qt4/ToolTipFormatter.h
@@ -0,0 +1,37 @@
+// -*- C++ -*-
+/**
+ * \file ToolTipFormatter.h
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author Guillaume Munch
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#ifndef TOOLTIPFORMATTER_H
+#define TOOLTIPFORMATTER_H
+
+#include <QObject>
+
+class QEvent;
+
+namespace lyx {
+namespace frontend {
+
+
+/// This event filter intercepts ToolTip events, to format any tooltip
+/// appropriately before display.
+class ToolTipFormatter : public QObject {
+       Q_OBJECT
+public:
+       ToolTipFormatter(QObject * parent);
+protected:
+       bool eventFilter(QObject * o, QEvent * e);
+};
+
+
+} // namespace frontend
+} // namespace lyx
+
+#endif // TOOLTIPFORMATTER_H
diff --git a/src/frontends/qt4/qt_helpers.cpp b/src/frontends/qt4/qt_helpers.cpp
index 5515af5..a2c7217 100644
--- a/src/frontends/qt4/qt_helpers.cpp
+++ b/src/frontends/qt4/qt_helpers.cpp
@@ -42,6 +42,9 @@
 #include <QLocale>
 #include <QPalette>
 #include <QSet>
+#include <QTextLayout>
+#include <QTextDocument>
+#include <QToolTip>
 
 #include <algorithm>
 #include <fstream>
@@ -647,4 +650,37 @@ QString guiName(string const & type, BufferParams const & 
bp)
 }
 
 
+QString formatToolTip(QString text, int em)
+{
+       // 1. QTooltip activates word wrapping only if mightBeRichText()
+       //    is true. So we convert the text to rich text.
+       //
+       // 2. The default width is way too small. Setting the width is tricky; 
first
+       //    one has to compute the ideal width, and then force it with special
+       //    html markup.
+
+       // do nothing if empty or already formatted
+       if (text.isEmpty() || text.startsWith(QString("<html>")))
+               return text;
+       // Convert to rich text if it is not already
+       if (!Qt::mightBeRichText(text))
+               text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
+       // Compute desired width in pixels
+       QFont const font = QToolTip::font();
+       int const px_width = em * QFontMetrics(font).width("M");
+       // Determine the ideal width of the tooltip
+    QTextDocument td("");
+    td.setHtml(text);
+    td.setDefaultFont(QToolTip::font());
+    td.setTextWidth(px_width);
+    double best_width = td.idealWidth();
+       // Set the line wrapping with appropriate width
+       return QString("<html><body><table><tr>"
+                      "<td align=justify width=%1>%2</td>"
+                      "</tr></table></body></html>")
+               .arg(QString::number(int(best_width) + 1), text);
+       return text;
+}
+
+
 } // namespace lyx
diff --git a/src/frontends/qt4/qt_helpers.h b/src/frontends/qt4/qt_helpers.h
index d4afc78..9f239a8 100644
--- a/src/frontends/qt4/qt_helpers.h
+++ b/src/frontends/qt4/qt_helpers.h
@@ -195,6 +195,24 @@ QString changeExtension(QString const & oldname, QString 
const & ext);
 /// parameter.
 QString guiName(std::string const & type, BufferParams const & bp);
 
+/// Format \param text for display as a ToolTip, breaking at lines of \param
+/// width ems. Note: this function is expensive. Better call it in a delayed
+/// manner, i.e. not to fill in a model (see for instance the function
+/// ToolTipFormatter::eventFilter).
+///
+/// When is it called automatically? Whenever the tooltip is not already rich
+/// text beginning with <html>, and is defined by the following functions:
+///  - QWidget::setToolTip(),
+///  - QAbstractItemModel::setData(..., Qt::ToolTipRole),
+///  - Inset::toolTip()
+///
+/// In other words, tooltips can use Qt html, and the tooltip will still be
+/// correctly broken. Moreover, it is possible to specify an entirely custom
+/// tooltip (not subject to automatic formatting) by giving it in its entirety,
+/// i.e. starting with <html>.
+QString formatToolTip(QString text, int width = 30);
+
+
 } // namespace lyx
 
 #endif // QTHELPERS_H

Reply via email to