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
3793fa09ff
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>.
This commit is contained in:
Guillaume Munch 2016-06-05 21:35:35 +02:00
parent 489dca71cd
commit 059ca0f691
6 changed files with 165 additions and 0 deletions

View File

@ -14,6 +14,7 @@
#include "GuiApplication.h" #include "GuiApplication.h"
#include "ToolTipFormatter.h"
#include "ColorCache.h" #include "ColorCache.h"
#include "ColorSet.h" #include "ColorSet.h"
#include "GuiClipboard.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 // This is clearly not enough in a time where we use threads for
// document preview and/or export. 20 should be OK. // document preview and/or export. 20 should be OK.
QThreadPool::globalInstance()->setMaxThreadCount(20); QThreadPool::globalInstance()->setMaxThreadCount(20);
// make sure tooltips are formatted
installEventFilter(new ToolTipFormatter(this));
} }

View File

@ -153,6 +153,7 @@ SOURCEFILES = \
TocModel.cpp \ TocModel.cpp \
TocWidget.cpp \ TocWidget.cpp \
Toolbars.cpp \ Toolbars.cpp \
ToolTipFormatter.cpp \
Validator.cpp Validator.cpp
NOMOCHEADER = \ NOMOCHEADER = \
@ -259,6 +260,7 @@ MOCHEADER = \
PanelStack.h \ PanelStack.h \
TocModel.h \ TocModel.h \
TocWidget.h \ TocWidget.h \
ToolTipFormatter.h \
Validator.h Validator.h
UIFILES = \ UIFILES = \

View File

@ -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"

View File

@ -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

View File

@ -42,6 +42,9 @@
#include <QLocale> #include <QLocale>
#include <QPalette> #include <QPalette>
#include <QSet> #include <QSet>
#include <QTextLayout>
#include <QTextDocument>
#include <QToolTip>
#include <algorithm> #include <algorithm>
#include <fstream> #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 } // namespace lyx

View File

@ -195,6 +195,24 @@ QString changeExtension(QString const & oldname, QString const & ext);
/// parameter. /// parameter.
QString guiName(std::string const & type, BufferParams const & bp); 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 } // namespace lyx
#endif // QTHELPERS_H #endif // QTHELPERS_H