Enablers for using QGlyphRun in SceneGraph
authorEskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@nokia.com>
Wed, 1 Jun 2011 13:33:38 +0000 (15:33 +0200)
committerQt by Nokia <qt-info@nokia.com>
Fri, 19 Aug 2011 08:18:04 +0000 (10:18 +0200)
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>
src/gui/text/qglyphrun.cpp
src/gui/text/qglyphrun.h
src/gui/text/qglyphrun_p.h
src/gui/text/qtextcontrol.cpp
src/gui/text/qtextcontrol_p.h
src/gui/text/qtextlayout.cpp
src/gui/text/qtextobject.cpp
src/gui/text/qtextobject.h
src/gui/widgets/qlinecontrol.cpp
src/gui/widgets/qlinecontrol_p.h

index cc82552..be9c058 100644 (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
index b4f02f0..da88bc7 100644 (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;
index a7745e6..b632678 100644 (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;
index 424d197..c29379e 100644 (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"
index cbf26d2..c5ed0ee 100644 (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);
 
index 2535fad..8c5e63d 100644 (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);
             }
         }
     }
index cea5eac..d641266 100644 (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);
index 9c5cc13..c2b46e4 100644 (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:
index 92c84d7..9b7d9b8 100644 (file)
@@ -76,6 +76,28 @@ static int qt_passwordEchoDelay = QT_GUI_PASSWORD_ECHO_DELAY;
 */
 
 /*!
+   \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
 
     Updates the display text based of the current edit text
@@ -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);
index 44bd721..d4c4154 100644 (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();