From b9224af92c128f7cc66bef11849db046f10dc81f Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Wed, 1 Jun 2011 15:33:38 +0200 Subject: 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 Reviewed-by: Gunnar Sletta Reviewed-by: Jiang Jiang --- src/gui/text/qglyphrun.cpp | 45 +++++++++++++++++++++++++++- src/gui/text/qglyphrun.h | 3 ++ src/gui/text/qglyphrun_p.h | 2 ++ src/gui/text/qtextcontrol.cpp | 24 ++++++++++++--- src/gui/text/qtextcontrol_p.h | 3 ++ src/gui/text/qtextlayout.cpp | 63 +++++++++++++++++++++++++++++++++------- src/gui/text/qtextobject.cpp | 17 ++++++----- src/gui/text/qtextobject.h | 2 +- src/gui/widgets/qlinecontrol.cpp | 43 +++++++++++++++++++-------- src/gui/widgets/qlinecontrol_p.h | 33 +++++++++++++++++++-- 10 files changed, 196 insertions(+), 39 deletions(-) (limited to 'src/gui') diff --git a/src/gui/text/qglyphrun.cpp b/src/gui/text/qglyphrun.cpp index cc825525c4..be9c058693 100644 --- a/src/gui/text/qglyphrun.cpp +++ b/src/gui/text/qglyphrun.cpp @@ -45,6 +45,7 @@ #include "qglyphrun.h" #include "qglyphrun_p.h" +#include 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; iglyphPositions.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 diff --git a/src/gui/text/qglyphrun.h b/src/gui/text/qglyphrun.h index b4f02f0d87..da88bc72f9 100644 --- a/src/gui/text/qglyphrun.h +++ b/src/gui/text/qglyphrun.h @@ -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; diff --git a/src/gui/text/qglyphrun_p.h b/src/gui/text/qglyphrun_p.h index a7745e68ce..b632678971 100644 --- a/src/gui/text/qglyphrun_p.h +++ b/src/gui/text/qglyphrun_p.h @@ -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 glyphIndexes; QVector glyphPositions; QRawFont rawFont; + QRectF boundingRect; uint overline : 1; uint underline : 1; diff --git a/src/gui/text/qtextcontrol.cpp b/src/gui/text/qtextcontrol.cpp index 424d1979b2..c29379ed28 100644 --- a/src/gui/text/qtextcontrol.cpp +++ b/src/gui/text/qtextcontrol.cpp @@ -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" diff --git a/src/gui/text/qtextcontrol_p.h b/src/gui/text/qtextcontrol_p.h index cbf26d2122..c5ed0ee1e0 100644 --- a/src/gui/text/qtextcontrol_p.h +++ b/src/gui/text/qtextcontrol_p.h @@ -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); diff --git a/src/gui/text/qtextlayout.cpp b/src/gui/text/qtextlayout.cpp index 2535faddc3..8c5e63da54 100644 --- a/src/gui/text/qtextlayout.cpp +++ b/src/gui/text/qtextlayout.cpp @@ -42,6 +42,7 @@ #include "qtextlayout.h" #include "qtextengine_p.h" +#include #include #include #include @@ -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 glyphs; QVector positions; for (int i=0; i QTextLine::glyphRuns(int from, int length) const { const QScriptLine &line = eng->lines[i]; @@ -2196,7 +2215,14 @@ QList QTextLine::glyphRuns(int from, int length) const if (length < 0) length = textLength(); - QTextLineItemIterator iterator(eng, i); + if (length == 0) + return QList(); + + QTextLayout::FormatRange selection; + selection.start = from; + selection.length = length; + + QTextLineItemIterator iterator(eng, i, QPointF(), &selection); qreal y = line.y.toReal() + line.base().toReal(); QList glyphRuns; while (!iterator.atEnd()) { @@ -2206,7 +2232,10 @@ QList 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 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; iglyphsEnd; --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 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 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 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); } } } diff --git a/src/gui/text/qtextobject.cpp b/src/gui/text/qtextobject.cpp index cea5eac465..d641266367 100644 --- a/src/gui/text/qtextobject.cpp +++ b/src/gui/text/qtextobject.cpp @@ -1667,21 +1667,24 @@ QTextBlock::iterator &QTextBlock::iterator::operator--() \sa QGlyphRun, QTextBlock::layout(), QTextLayout::position(), QPainter::drawGlyphRun() */ #if !defined(QT_NO_RAWFONT) -QList QTextFragment::glyphRuns() const +QList QTextFragment::glyphRuns(int pos, int len) const { if (!p || !n) return QList(); - int pos = position(); - int len = length(); - if (len == 0) - return QList(); - - 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(); + QList ret; for (int i=0; ilineCount(); ++i) { QTextLine textLine = layout->lineAt(i); diff --git a/src/gui/text/qtextobject.h b/src/gui/text/qtextobject.h index 9c5cc13539..c2b46e4d12 100644 --- a/src/gui/text/qtextobject.h +++ b/src/gui/text/qtextobject.h @@ -317,7 +317,7 @@ public: QString text() const; #if !defined(QT_NO_RAWFONT) - QList glyphRuns() const; + QList glyphRuns(int from = -1, int length = -1) const; #endif private: diff --git a/src/gui/widgets/qlinecontrol.cpp b/src/gui/widgets/qlinecontrol.cpp index 92c84d72e0..9b7d9b83ad 100644 --- a/src/gui/widgets/qlinecontrol.cpp +++ b/src/gui/widgets/qlinecontrol.cpp @@ -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); diff --git a/src/gui/widgets/qlinecontrol_p.h b/src/gui/widgets/qlinecontrol_p.h index 44bd7214be..d4c4154b3d 100644 --- a/src/gui/widgets/qlinecontrol_p.h +++ b/src/gui/widgets/qlinecontrol_p.h @@ -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(); -- cgit v1.2.3