Enablers for using QGlyphRun in SceneGraph

Several required fixes and changes to fix problems that arose
when porting SceneGraph's QSGTextInput and QSGTextEdit to
use QSGTextNode, especially related to having selections
on the text.

Also fixes crashes with the threaded renderer on Mac OS X
when using the TextEdit or TextInput elements.

Task-number: QTBUG-18019, QTBUG-20017
Change-Id: I67f24465352daa1d2cb12b6d2f378feb676c9804
Reviewed-on: http://codereview.qt.nokia.com/2864
Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com>
Reviewed-by: Gunnar Sletta <gunnar.sletta@nokia.com>
Reviewed-by: Jiang Jiang <jiang.jiang@nokia.com>
bb10
Eskil Abrahamsen Blomfeldt 2011-06-01 15:33:38 +02:00 committed by Qt by Nokia
parent 1e0d1c5f29
commit b9224af92c
10 changed files with 196 additions and 39 deletions

View File

@ -45,6 +45,7 @@
#include "qglyphrun.h"
#include "qglyphrun_p.h"
#include <qdebug.h>
QT_BEGIN_NAMESPACE
@ -343,12 +344,44 @@ void QGlyphRun::setStrikeOut(bool strikeOut)
}
/*!
Returns the smallest rectangle that contains all glyphs in this QGlyphRun.
Sets the bounding rect of the glyphs in this QGlyphRun to be \a boundingRect. This rectangle
will be returned by boundingRect() unless it is empty, in which case the bounding rectangle of the
glyphs in the glyph run will be returned instead.
\note Unless you are implementing text shaping, you should not have to use this function.
It is used specifically when the QGlyphRun should represent an area which is smaller than the
area of the glyphs it contains. This could happen e.g. if the glyph run is retrieved by calling
QTextLayout::glyphRuns() and the specified range only includes part of a ligature (where two or
more characters are combined to a single glyph.) When this is the case, the bounding rect should
only include the appropriate part of the ligature glyph, based on a calculation of the average
width of the characters in the ligature.
In order to support such a case (an example is selections which should be drawn with a different
color than the main text color), it is necessary to clip the painting mechanism to the rectangle
returned from boundingRect() to avoid drawing the entire ligature glyph.
\sa boundingRect()
\since 5.0
*/
void QGlyphRun::setBoundingRect(const QRectF &boundingRect)
{
detach();
d->boundingRect = boundingRect;
}
/*!
Returns the smallest rectangle that contains all glyphs in this QGlyphRun. If a bounding rect
has been set using setBoundingRect(), then this will be returned. Otherwise the bounding rect
will be calculated based on the font metrics of the glyphs in the glyph run.
\since 5.0
*/
QRectF QGlyphRun::boundingRect() const
{
if (!d->boundingRect.isEmpty())
return d->boundingRect;
qreal minX, minY, maxX, maxY;
for (int i=0; i<qMin(d->glyphPositions.size(), d->glyphIndexes.size()); ++i) {
@ -371,6 +404,16 @@ QRectF QGlyphRun::boundingRect() const
return QRectF(QPointF(minX, minY), QPointF(maxX, maxY));
}
/*!
Returns true if the QGlyphRun does not contain any glyphs.
\since 5.0
*/
bool QGlyphRun::isEmpty() const
{
return d->glyphIndexes.isEmpty();
}
QT_END_NAMESPACE
#endif // QT_NO_RAWFONT

View File

@ -91,8 +91,11 @@ public:
void setStrikeOut(bool strikeOut);
bool strikeOut() const;
void setBoundingRect(const QRectF &boundingRect);
QRectF boundingRect() const;
bool isEmpty() const;
private:
friend class QGlyphRunPrivate;
friend class QTextLine;

View File

@ -83,6 +83,7 @@ public:
, glyphIndexes(other.glyphIndexes)
, glyphPositions(other.glyphPositions)
, rawFont(other.rawFont)
, boundingRect(other.boundingRect)
, overline(other.overline)
, underline(other.underline)
, strikeOut(other.strikeOut)
@ -96,6 +97,7 @@ public:
QVector<quint32> glyphIndexes;
QVector<QPointF> glyphPositions;
QRawFont rawFont;
QRectF boundingRect;
uint overline : 1;
uint underline : 1;

View File

@ -409,6 +409,8 @@ void QTextControlPrivate::init(Qt::TextFormat format, const QString &text, QText
doc->setUndoRedoEnabled(interactionFlags & Qt::TextEditable);
q->setCursorWidth(-1);
QObject::connect(q, SIGNAL(updateCursorRequest(QRectF)), q, SIGNAL(updateRequest(QRectF)));
}
void QTextControlPrivate::setContent(Qt::TextFormat format, const QString &text, QTextDocument *document)
@ -547,7 +549,7 @@ void QTextControlPrivate::setCursorPosition(int pos, QTextCursor::MoveMode mode)
void QTextControlPrivate::repaintCursor()
{
Q_Q(QTextControl);
emit q->updateRequest(cursorRectPlusUnicodeDirectionMarkers(cursor));
emit q->updateCursorRequest(cursorRectPlusUnicodeDirectionMarkers(cursor));
}
void QTextControlPrivate::repaintOldAndNewSelection(const QTextCursor &oldSelection)
@ -565,9 +567,16 @@ void QTextControlPrivate::repaintOldAndNewSelection(const QTextCursor &oldSelect
differenceSelection.setPosition(cursor.position(), QTextCursor::KeepAnchor);
emit q->updateRequest(q->selectionRect(differenceSelection));
} else {
if (!oldSelection.isNull())
emit q->updateRequest(q->selectionRect(oldSelection) | cursorRectPlusUnicodeDirectionMarkers(oldSelection));
emit q->updateRequest(q->selectionRect() | cursorRectPlusUnicodeDirectionMarkers(cursor));
if (!oldSelection.hasSelection() && !cursor.hasSelection()) {
if (!oldSelection.isNull())
emit q->updateCursorRequest(q->selectionRect(oldSelection) | cursorRectPlusUnicodeDirectionMarkers(oldSelection));
emit q->updateCursorRequest(q->selectionRect() | cursorRectPlusUnicodeDirectionMarkers(cursor));
} else {
if (!oldSelection.isNull())
emit q->updateRequest(q->selectionRect(oldSelection) | cursorRectPlusUnicodeDirectionMarkers(oldSelection));
emit q->updateRequest(q->selectionRect() | cursorRectPlusUnicodeDirectionMarkers(cursor));
}
}
}
@ -2959,6 +2968,12 @@ void QTextControl::setPalette(const QPalette &pal)
d->palette = pal;
}
bool QTextControl::cursorOn() const
{
Q_D(const QTextControl);
return d->cursorOn;
}
QAbstractTextDocumentLayout::PaintContext QTextControl::getPaintContext(QWidget *widget) const
{
Q_D(const QTextControl);
@ -3146,6 +3161,7 @@ void QTextEditMimeData::setup() const
fragment = QTextDocumentFragment();
}
QT_END_NAMESPACE
#include "moc_qtextcontrol_p.cpp"

View File

@ -226,6 +226,7 @@ Q_SIGNALS:
void cursorPositionChanged();
// control signals
void updateCursorRequest(const QRectF &rect = QRectF());
void updateRequest(const QRectF &rect = QRectF());
void documentSizeChanged(const QSizeF &);
void blockCountChanged(int newBlockCount);
@ -258,6 +259,8 @@ public:
bool setFocusToNextOrPreviousAnchor(bool next);
bool findNextPrevAnchor(const QTextCursor& from, bool next, QTextCursor& newAnchor);
bool cursorOn() const;
protected:
virtual void timerEvent(QTimerEvent *e);

View File

@ -42,6 +42,7 @@
#include "qtextlayout.h"
#include "qtextengine_p.h"
#include <qthread.h>
#include <qfont.h>
#include <qapplication.h>
#include <qpainter.h>
@ -2108,8 +2109,10 @@ static void setPenAndDrawBackground(QPainter *p, const QPen &defaultPen, const Q
}
#if !defined(QT_NO_RAWFONT)
static QGlyphRun glyphRunWithInfo(QFontEngine *fontEngine, const QGlyphLayout &glyphLayout,
const QPointF &pos, const QTextItem::RenderFlags &flags)
const QPointF &pos, const QTextItem::RenderFlags &flags,
const QFixed &selectionX, const QFixed &selectionWidth)
{
QGlyphRun glyphRun;
@ -2117,6 +2120,7 @@ static QGlyphRun glyphRunWithInfo(QFontEngine *fontEngine, const QGlyphLayout &g
QRawFont font;
QRawFontPrivate *fontD = QRawFontPrivate::get(font);
fontD->fontEngine = fontEngine;
fontD->thread = QThread::currentThread();
fontD->fontEngine->ref.ref();
#if defined(Q_WS_WIN)
@ -2151,13 +2155,27 @@ static QGlyphRun glyphRunWithInfo(QFontEngine *fontEngine, const QGlyphLayout &g
positionsArray);
Q_ASSERT(glyphsArray.size() == positionsArray.size());
qreal fontHeight = font.ascent() + font.descent();
qreal minY;
qreal maxY;
QVector<quint32> glyphs;
QVector<QPointF> positions;
for (int i=0; i<glyphsArray.size(); ++i) {
glyphs.append(glyphsArray.at(i) & 0xffffff);
positions.append(positionsArray.at(i).toPointF() + pos);
QPointF position = positionsArray.at(i).toPointF() + pos;
positions.append(position);
if (i == 0) {
maxY = minY = position.y();
} else {
minY = qMin(minY, position.y());
maxY = qMax(maxY, position.y());
}
}
qreal height = maxY + fontHeight - minY;
glyphRun.setGlyphIndexes(glyphs);
glyphRun.setPositions(positions);
@ -2166,6 +2184,8 @@ static QGlyphRun glyphRunWithInfo(QFontEngine *fontEngine, const QGlyphLayout &g
glyphRun.setStrikeOut(flags.testFlag(QTextItem::StrikeOut));
glyphRun.setRawFont(font);
glyphRun.setBoundingRect(QRectF(selectionX.toReal(), minY, selectionWidth.toReal(), height));
return glyphRun;
}
@ -2182,7 +2202,6 @@ static QGlyphRun glyphRunWithInfo(QFontEngine *fontEngine, const QGlyphLayout &g
\sa QTextLayout::glyphRuns()
*/
#if !defined(QT_NO_RAWFONT)
QList<QGlyphRun> QTextLine::glyphRuns(int from, int length) const
{
const QScriptLine &line = eng->lines[i];
@ -2196,7 +2215,14 @@ QList<QGlyphRun> QTextLine::glyphRuns(int from, int length) const
if (length < 0)
length = textLength();
QTextLineItemIterator iterator(eng, i);
if (length == 0)
return QList<QGlyphRun>();
QTextLayout::FormatRange selection;
selection.start = from;
selection.length = length;
QTextLineItemIterator iterator(eng, i, QPointF(), &selection);
qreal y = line.y.toReal() + line.base().toReal();
QList<QGlyphRun> glyphRuns;
while (!iterator.atEnd()) {
@ -2206,7 +2232,10 @@ QList<QGlyphRun> QTextLine::glyphRuns(int from, int length) const
QPointF pos(iterator.x.toReal(), y);
if (from >= 0 && length >= 0 &&
(from >= si.position + eng->length(&si) || from + length <= si.position)) {
(from >= si.position + eng->length(&si)
|| from + length <= si.position
|| from + length <= iterator.itemStart
|| from >= iterator.itemEnd)) {
continue;
}
@ -2235,19 +2264,22 @@ QList<QGlyphRun> QTextLine::glyphRuns(int from, int length) const
? si.num_glyphs - 1
: logClusters[relativeTo];
int itemGlyphsStart = logClusters[iterator.itemStart - si.position];
int itemGlyphsEnd = logClusters[iterator.itemEnd - 1 - si.position];
QGlyphLayout glyphLayout = eng->shapedGlyphs(&si);
// Calculate new x position of glyph layout for a subset. This becomes somewhat complex
// when we're breaking a RTL script item, since the expected position passed into
// getGlyphPositions() is the left-most edge of the left-most glyph in an RTL run.
if (relativeFrom != (iterator.itemStart - si.position) && !rtl) {
for (int i=0; i<glyphsStart; ++i) {
for (int i=itemGlyphsStart; i<glyphsStart; ++i) {
QFixed justification = QFixed::fromFixed(glyphLayout.justifications[i].space_18d6);
pos += QPointF((glyphLayout.advances_x[i] + justification).toReal(),
glyphLayout.advances_y[i].toReal());
}
} else if (relativeTo != (iterator.itemEnd - si.position - 1) && rtl) {
for (int i=glyphLayout.numGlyphs - 1; i>glyphsEnd; --i) {
for (int i=itemGlyphsEnd; i>glyphsEnd; --i) {
QFixed justification = QFixed::fromFixed(glyphLayout.justifications[i].space_18d6);
pos += QPointF((glyphLayout.advances_x[i] + justification).toReal(),
glyphLayout.advances_y[i].toReal());
@ -2256,6 +2288,10 @@ QList<QGlyphRun> QTextLine::glyphRuns(int from, int length) const
glyphLayout = glyphLayout.mid(glyphsStart, glyphsEnd - glyphsStart + 1);
QFixed x;
QFixed width;
iterator.getSelectionBounds(&x, &width);
if (glyphLayout.numGlyphs > 0) {
QFontEngine *mainFontEngine = font.d->engineForScript(si.analysis.script);
if (mainFontEngine->type() == QFontEngine::Multi) {
@ -2270,7 +2306,7 @@ QList<QGlyphRun> QTextLine::glyphRuns(int from, int length) const
QGlyphLayout subLayout = glyphLayout.mid(start, end - start);
glyphRuns.append(glyphRunWithInfo(multiFontEngine->engine(which),
subLayout, pos, flags));
subLayout, pos, flags, x, width));
for (int i = 0; i < subLayout.numGlyphs; i++) {
pos += QPointF(subLayout.advances_x[i].toReal(),
subLayout.advances_y[i].toReal());
@ -2281,10 +2317,15 @@ QList<QGlyphRun> QTextLine::glyphRuns(int from, int length) const
}
QGlyphLayout subLayout = glyphLayout.mid(start, end - start);
glyphRuns.append(glyphRunWithInfo(multiFontEngine->engine(which),
subLayout, pos, flags));
QGlyphRun glyphRun = glyphRunWithInfo(multiFontEngine->engine(which),
subLayout, pos, flags, x, width);
if (!glyphRun.isEmpty())
glyphRuns.append(glyphRun);
} else {
glyphRuns.append(glyphRunWithInfo(mainFontEngine, glyphLayout, pos, flags));
QGlyphRun glyphRun = glyphRunWithInfo(mainFontEngine, glyphLayout, pos, flags, x,
width);
if (!glyphRun.isEmpty())
glyphRuns.append(glyphRun);
}
}
}

View File

@ -1667,21 +1667,24 @@ QTextBlock::iterator &QTextBlock::iterator::operator--()
\sa QGlyphRun, QTextBlock::layout(), QTextLayout::position(), QPainter::drawGlyphRun()
*/
#if !defined(QT_NO_RAWFONT)
QList<QGlyphRun> QTextFragment::glyphRuns() const
QList<QGlyphRun> QTextFragment::glyphRuns(int pos, int len) const
{
if (!p || !n)
return QList<QGlyphRun>();
int pos = position();
int len = length();
if (len == 0)
return QList<QGlyphRun>();
int blockNode = p->blockMap().findNode(pos);
int blockNode = p->blockMap().findNode(position());
const QTextBlockData *blockData = p->blockMap().fragment(blockNode);
QTextLayout *layout = blockData->layout;
int blockPosition = p->blockMap().position(blockNode);
if (pos < 0)
pos = position() - blockPosition;
if (len < 0)
len = length();
if (len == 0)
return QList<QGlyphRun>();
QList<QGlyphRun> ret;
for (int i=0; i<layout->lineCount(); ++i) {
QTextLine textLine = layout->lineAt(i);

View File

@ -317,7 +317,7 @@ public:
QString text() const;
#if !defined(QT_NO_RAWFONT)
QList<QGlyphRun> glyphRuns() const;
QList<QGlyphRun> glyphRuns(int from = -1, int length = -1) const;
#endif
private:

View File

@ -75,6 +75,28 @@ static int qt_passwordEchoDelay = QT_GUI_PASSWORD_ECHO_DELAY;
password characters.
*/
/*!
\internal
Updates the internal text layout. Returns the ascent of the
created QTextLine.
*/
int QLineControl::redoTextLayout() const
{
m_textLayout.clearLayout();
m_textLayout.beginLayout();
QTextLine l = m_textLayout.createLine();
m_textLayout.endLayout();
#if defined(Q_WS_MAC)
if (m_threadChecks)
m_textLayoutThread = QThread::currentThread();
#endif
return qRound(l.ascent());
}
/*!
\internal
@ -124,15 +146,12 @@ void QLineControl::updateDisplayText(bool forceUpdate)
m_textLayout.setText(str);
QTextOption option;
QTextOption option = m_textLayout.textOption();
option.setTextDirection(m_layoutDirection);
option.setFlags(QTextOption::IncludeTrailingSpaces);
m_textLayout.setTextOption(option);
m_textLayout.beginLayout();
QTextLine l = m_textLayout.createLine();
m_textLayout.endLayout();
m_ascent = qRound(l.ascent());
m_ascent = redoTextLayout();
if (str != orig || forceUpdate)
emit displayTextChanged(str);
@ -228,7 +247,7 @@ void QLineControl::del()
if (hasSelectedText()) {
removeSelectedText();
} else {
int n = m_textLayout.nextCursorPosition(m_cursor) - m_cursor;
int n = textLayout()->nextCursorPosition(m_cursor) - m_cursor;
while (n--)
internalDelete();
}
@ -357,7 +376,7 @@ void QLineControl::updatePasswordEchoEditing(bool editing)
*/
int QLineControl::xToPos(int x, QTextLine::CursorPosition betweenOrOn) const
{
return m_textLayout.lineAt(0).xToCursor(x, betweenOrOn);
return textLayout()->lineAt(0).xToCursor(x, betweenOrOn);
}
/*!
@ -368,7 +387,7 @@ int QLineControl::xToPos(int x, QTextLine::CursorPosition betweenOrOn) const
*/
QRect QLineControl::cursorRect() const
{
QTextLine l = m_textLayout.lineAt(0);
QTextLine l = textLayout()->lineAt(0);
int c = m_cursor;
if (m_preeditCursor != -1)
c += m_preeditCursor;
@ -578,14 +597,14 @@ void QLineControl::draw(QPainter *painter, const QPoint &offset, const QRect &cl
}
if (flags & DrawText)
m_textLayout.draw(painter, offset, selections, clip);
textLayout()->draw(painter, offset, selections, clip);
if (flags & DrawCursor){
int cursor = m_cursor;
if (m_preeditCursor != -1)
cursor += m_preeditCursor;
if (!m_hideCursor && (!m_blinkPeriod || m_blinkStatus))
m_textLayout.drawCursor(painter, offset, cursor, m_cursorWidth);
textLayout()->drawCursor(painter, offset, cursor, m_cursorWidth);
}
}
@ -601,10 +620,10 @@ void QLineControl::selectWordAtPos(int cursor)
int next = cursor + 1;
if(next > end())
--next;
int c = m_textLayout.previousCursorPosition(next, QTextLayout::SkipWords);
int c = textLayout()->previousCursorPosition(next, QTextLayout::SkipWords);
moveCursor(c, false);
// ## text layout should support end of words.
int end = m_textLayout.nextCursorPosition(c, QTextLayout::SkipWords);
int end = textLayout()->nextCursorPosition(c, QTextLayout::SkipWords);
while (end > cursor && m_text[end-1].isSpace())
--end;
moveCursor(end, true);

View File

@ -65,6 +65,7 @@
#include "QtCore/qpoint.h"
#include "QtGui/qcompleter.h"
#include "QtGui/qaccessible.h"
#include "QtCore/qthread.h"
#include "qplatformdefs.h"
@ -90,6 +91,10 @@ public:
#ifdef QT_GUI_PASSWORD_ECHO_DELAY
, m_passwordEchoTimer(0)
#endif
#if defined(Q_WS_MAC)
, m_threadChecks(false)
, m_textLayoutThread(0)
#endif
{
init(txt);
}
@ -332,11 +337,27 @@ public:
bool processEvent(QEvent *ev);
QTextLayout *textLayout()
QTextLayout *textLayout() const
{
#if defined(Q_WS_MAC)
if (m_threadChecks && QThread::currentThread() != m_textLayoutThread)
redoTextLayout();
#endif
return &m_textLayout;
}
#if defined(Q_WS_MAC)
void setThreadChecks(bool threadChecks)
{
m_threadChecks = threadChecks;
}
bool threadChecks() const
{
return m_threadChecks;
}
#endif
private:
void init(const QString &txt);
void removeSelectedText();
@ -433,8 +454,8 @@ private:
QString stripString(const QString &str) const;
int findInMask(int pos, bool forward, bool findSeparator, QChar searchChar = QChar()) const;
// complex text layout
QTextLayout m_textLayout;
// complex text layout (must be mutable so it can be reshaped at will)
mutable QTextLayout m_textLayout;
bool m_passwordEchoEditing;
QChar m_passwordCharacter;
@ -451,6 +472,12 @@ private:
#endif
}
int redoTextLayout() const;
#if defined(Q_WS_MAC)
bool m_threadChecks;
mutable QThread *m_textLayoutThread;
#endif
Q_SIGNALS:
void cursorPositionChanged(int, int);
void selectionChanged();