Support visual cursor movement for BIDI text

Bidi input can in some contexts be more intuitive if the cursor
works in visual way: pressing left arrow key always make cursor
move one character to the left regardless the language of text,
pressing right arrow key always make cursor move to the right.
It is also the behavior of Mac OS X. Based on the above reason
and requests from Symbian we implemented this support for visual
movement in BIDI text. 3 public properties are added to
QTextDocument, QTextLayout and QLineEdit respectively:

- QTextDocument::defaultCursorMoveStyle can be used to control
  the cursor behavior in all widgets based on QTextDocument,
  like QTextEdit, QPlainTextEdit, etc. When set to QTextCursor::
  Visual, it will enable visual movement for all the cursors in
  the corresponding text edit. Default is QTextCursor::Logical.

- QTextLayout::cursorMoveStyle is used for low-level cursor
  manipulation. When set to Visual, it will enable visual movement
  behavior for all the cursor related methods, including cursorToX,
  xToCursor and drawCursor. Default is Logical.

- QLineEdit::cursorMoveStyle is used to control cursor movement
  behavior in QLineEdit. Default is Logical.:

Task-number: QTBUG-13859
Reviewed-by: Eskil
(cherry picked from commit c480dd641f5d22d1ee72cb27bf39e24c6df65658)
This commit is contained in:
Jiang Jiang 2010-12-15 15:11:45 +01:00
parent 028aa80d70
commit 0f7cba14f6
17 changed files with 998 additions and 273 deletions

View File

@ -362,20 +362,23 @@ bool QTextCursorPrivate::movePosition(QTextCursor::MoveOperation op, QTextCursor
currentCharFormat = -1;
bool adjustX = true;
QTextBlock blockIt = block();
bool visualMovement = priv->defaultCursorMoveStyle == QTextCursor::Visual;
if (!blockIt.isValid())
return false;
if (op >= QTextCursor::Left && op <= QTextCursor::WordRight
&& blockIt.textDirection() == Qt::RightToLeft) {
if (op == QTextCursor::Left)
op = QTextCursor::NextCharacter;
else if (op == QTextCursor::Right)
op = QTextCursor::PreviousCharacter;
else if (op == QTextCursor::WordLeft)
if (blockIt.textDirection() == Qt::RightToLeft) {
if (op == QTextCursor::WordLeft)
op = QTextCursor::NextWord;
else if (op == QTextCursor::WordRight)
op = QTextCursor::PreviousWord;
if (!visualMovement) {
if (op == QTextCursor::Left)
op = QTextCursor::NextCharacter;
else if (op == QTextCursor::Right)
op = QTextCursor::PreviousCharacter;
}
}
const QTextLayout *layout = blockLayout(blockIt);
@ -418,9 +421,12 @@ bool QTextCursorPrivate::movePosition(QTextCursor::MoveOperation op, QTextCursor
break;
}
case QTextCursor::PreviousCharacter:
case QTextCursor::Left:
newPosition = priv->previousCursorPosition(position, QTextLayout::SkipCharacters);
break;
case QTextCursor::Left:
newPosition = visualMovement ? priv->leftCursorPosition(position)
: priv->previousCursorPosition(position, QTextLayout::SkipCharacters);
break;
case QTextCursor::StartOfWord: {
if (relativePos == 0)
break;
@ -529,9 +535,12 @@ bool QTextCursorPrivate::movePosition(QTextCursor::MoveOperation op, QTextCursor
break;
}
case QTextCursor::NextCharacter:
case QTextCursor::Right:
newPosition = priv->nextCursorPosition(position, QTextLayout::SkipCharacters);
break;
case QTextCursor::Right:
newPosition = visualMovement ? priv->rightCursorPosition(position)
: priv->nextCursorPosition(position, QTextLayout::SkipCharacters);
break;
case QTextCursor::NextWord:
case QTextCursor::WordRight:
newPosition = priv->nextCursorPosition(position, QTextLayout::SkipWords);
@ -2558,4 +2567,19 @@ QTextDocument *QTextCursor::document() const
return 0; // document went away
}
/*!
\enum QTextCursor::MoveStyle
This enum describes the movement style available to QTextCursor. The options
are:
\value Logical Within a left-to-right text block, increase cursor position
when pressing left arrow key, decrease cursor position when pressing the
right arrow key. If the text block is right-to-left, the opposite behavior
applies.
\value Visual Pressing the left arrow key will always cause the cursor to move
left, regardless of the text's writing direction. The same behavior applies to
right arrow key.
*/
QT_END_NAMESPACE

View File

@ -86,6 +86,10 @@ public:
MoveAnchor,
KeepAnchor
};
enum MoveStyle {
Logical,
Visual,
};
void setPosition(int pos, MoveMode mode = MoveAnchor);
int position() const;

View File

@ -585,6 +585,29 @@ void QTextDocument::setDefaultTextOption(const QTextOption &option)
d->lout->documentChanged(0, 0, d->length());
}
/*!
\since 4.8
The default cursor movement style is used by all QTextCursor objects
created from the document. The default is QTextCursor::Logical.
*/
QTextCursor::MoveStyle QTextDocument::defaultCursorMoveStyle() const
{
Q_D(const QTextDocument);
return d->defaultCursorMoveStyle;
}
/*!
\since 4.8
Set the default cursor movement style.
*/
void QTextDocument::setDefaultCursorMoveStyle(QTextCursor::MoveStyle style)
{
Q_D(QTextDocument);
d->defaultCursorMoveStyle = style;
}
/*!
\fn void QTextDocument::markContentsDirty(int position, int length)

View File

@ -46,6 +46,7 @@
#include <QtCore/qsize.h>
#include <QtCore/qrect.h>
#include <QtGui/qfont.h>
#include <QtGui/qtextcursor.h>
QT_BEGIN_HEADER
@ -60,7 +61,6 @@ class QPainter;
class QPrinter;
class QAbstractTextDocumentLayout;
class QPoint;
class QTextCursor;
class QTextObject;
class QTextFormat;
class QTextFrame;
@ -269,6 +269,9 @@ public:
QTextOption defaultTextOption() const;
void setDefaultTextOption(const QTextOption &option);
QTextCursor::MoveStyle defaultCursorMoveStyle() const;
void setDefaultCursorMoveStyle(QTextCursor::MoveStyle style);
Q_SIGNALS:
void contentsChange(int from, int charsRemoves, int charsAdded);
void contentsChanged();

View File

@ -209,6 +209,7 @@ QTextDocumentPrivate::QTextDocumentPrivate()
defaultTextOption.setTabStop(80); // same as in qtextengine.cpp
defaultTextOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
defaultCursorMoveStyle = QTextCursor::Logical;
indentWidth = 40;
documentMargin = 4;
@ -1382,6 +1383,20 @@ int QTextDocumentPrivate::previousCursorPosition(int position, QTextLayout::Curs
return it.layout()->previousCursorPosition(position-start, mode) + start;
}
int QTextDocumentPrivate::leftCursorPosition(int position) const
{
QTextBlock it = blocksFind(position);
int start = it.position();
return it.layout()->leftCursorPosition(position-start) + start;
}
int QTextDocumentPrivate::rightCursorPosition(int position) const
{
QTextBlock it = blocksFind(position);
int start = it.position();
return it.layout()->rightCursorPosition(position-start) + start;
}
void QTextDocumentPrivate::changeObjectFormat(QTextObject *obj, int format)
{
beginEditBlock();

View File

@ -64,6 +64,7 @@
#include "private/qtextformat_p.h"
#include "QtGui/qtextdocument.h"
#include "QtGui/qtextobject.h"
#include "QtGui/qtextcursor.h"
#include "QtCore/qmap.h"
#include "QtCore/qvariant.h"
#include "QtCore/qurl.h"
@ -244,6 +245,8 @@ public:
int nextCursorPosition(int position, QTextLayout::CursorMode mode) const;
int previousCursorPosition(int position, QTextLayout::CursorMode mode) const;
int leftCursorPosition(int position) const;
int rightCursorPosition(int position) const;
void changeObjectFormat(QTextObject *group, int format);
@ -339,6 +342,7 @@ private:
public:
QTextOption defaultTextOption;
QTextCursor::MoveStyle defaultCursorMoveStyle;
#ifndef QT_NO_CSSPARSER
QCss::StyleSheet parsedDefaultStyleSheet;
#endif

View File

@ -1304,6 +1304,7 @@ static void init(QTextEngine *e)
e->ignoreBidi = false;
e->cacheGlyphs = false;
e->forceJustification = false;
e->visualMovement = false;
e->layoutData = 0;
@ -2737,6 +2738,180 @@ QFixed QTextEngine::leadingSpaceWidth(const QScriptLine &line)
return width(line.from + pos, line.length - pos);
}
QFixed QTextEngine::alignLine(const QScriptLine &line)
{
QFixed x = 0;
justify(line);
// if width is QFIXED_MAX that means we used setNumColumns() and that implicitly makes this line left aligned.
if (!line.justified && line.width != QFIXED_MAX) {
int align = option.alignment();
if (align & Qt::AlignJustify && isRightToLeft())
align = Qt::AlignRight;
if (align & Qt::AlignRight)
x = line.width - (line.textAdvance + leadingSpaceWidth(line));
else if (align & Qt::AlignHCenter)
x = (line.width - line.textAdvance)/2;
}
return x;
}
QFixed QTextEngine::offsetInLigature(const QScriptItem *si, int pos, int max, int glyph_pos)
{
unsigned short *logClusters = this->logClusters(si);
const QGlyphLayout &glyphs = shapedGlyphs(si);
int offsetInCluster = 0;
for (int i = pos - 1; i >= 0; i--) {
if (logClusters[i] == glyph_pos)
offsetInCluster++;
else
break;
}
// in the case that the offset is inside a (multi-character) glyph,
// interpolate the position.
if (offsetInCluster > 0) {
int clusterLength = 0;
for (int i = pos - offsetInCluster; i < max; i++) {
if (logClusters[i] == glyph_pos)
clusterLength++;
else
break;
}
if (clusterLength)
return glyphs.advances_x[glyph_pos] * offsetInCluster / clusterLength;
}
return 0;
}
int QTextEngine::previousLogicalPosition(int oldPos) const
{
const HB_CharAttributes *attrs = attributes();
if (!attrs || oldPos < 0)
return oldPos;
if (oldPos <= 0)
return 0;
oldPos--;
while (oldPos && !attrs[oldPos].charStop)
oldPos--;
return oldPos;
}
int QTextEngine::nextLogicalPosition(int oldPos) const
{
const HB_CharAttributes *attrs = attributes();
int len = block.isValid() ? block.length() - 1
: layoutData->string.length();
Q_ASSERT(len <= layoutData->string.length());
if (!attrs || oldPos < 0 || oldPos >= len)
return oldPos;
oldPos++;
while (oldPos < len && !attrs[oldPos].charStop)
oldPos++;
return oldPos;
}
int QTextEngine::lineNumberForTextPosition(int pos)
{
if (!layoutData)
itemize();
if (pos == layoutData->string.length() && lines.size())
return lines.size() - 1;
for (int i = 0; i < lines.size(); ++i) {
const QScriptLine& line = lines[i];
if (line.from + line.length > pos)
return i;
}
return -1;
}
void QTextEngine::insertionPointsForLine(int lineNum, QVector<int> &insertionPoints)
{
QTextLineItemIterator iterator(this, lineNum);
bool rtl = isRightToLeft();
bool lastLine = lineNum >= lines.size() - 1;
while (!iterator.atEnd()) {
iterator.next();
const QScriptItem *si = &layoutData->items[iterator.item];
if (si->analysis.bidiLevel % 2) {
int i = iterator.itemEnd - 1, min = iterator.itemStart;
if (lastLine && (rtl ? iterator.atBeginning() : iterator.atEnd()))
i++;
for (; i >= min; i--)
insertionPoints.push_back(i);
} else {
int i = iterator.itemStart, max = iterator.itemEnd;
if (lastLine && (rtl ? iterator.atBeginning() : iterator.atEnd()))
max++;
for (; i < max; i++)
insertionPoints.push_back(i);
}
}
}
int QTextEngine::endOfLine(int lineNum)
{
QVector<int> insertionPoints;
insertionPointsForLine(lineNum, insertionPoints);
if (insertionPoints.size() > 0)
return insertionPoints.last();
return 0;
}
int QTextEngine::beginningOfLine(int lineNum)
{
QVector<int> insertionPoints;
insertionPointsForLine(lineNum, insertionPoints);
if (insertionPoints.size() > 0)
return insertionPoints.first();
return 0;
}
int QTextEngine::positionAfterVisualMovement(int pos, QTextCursor::MoveOperation op)
{
if (!layoutData)
itemize();
bool moveRight = (op == QTextCursor::Right);
bool alignRight = isRightToLeft();
if (!layoutData->hasBidi)
return moveRight ^ alignRight ? nextLogicalPosition(pos) : previousLogicalPosition(pos);
int lineNum = lineNumberForTextPosition(pos);
Q_ASSERT(lineNum >= 0);
QVector<int> insertionPoints;
insertionPointsForLine(lineNum, insertionPoints);
int i, max = insertionPoints.size();
for (i = 0; i < max; i++)
if (pos == insertionPoints[i]) {
if (moveRight) {
if (i + 1 < max)
return insertionPoints[i + 1];
} else {
if (i > 0)
return insertionPoints[i - 1];
}
if (moveRight ^ alignRight) {
if (lineNum + 1 < lines.size())
return alignRight ? endOfLine(lineNum + 1) : beginningOfLine(lineNum + 1);
}
else {
if (lineNum > 0)
return alignRight ? beginningOfLine(lineNum - 1) : endOfLine(lineNum - 1);
}
}
return pos;
}
QStackTextEngine::QStackTextEngine(const QString &string, const QFont &f)
: QTextEngine(string, f),
_layoutData(string, _memory, MemSize)
@ -2841,5 +3016,127 @@ glyph_metrics_t glyph_metrics_t::transformed(const QTransform &matrix) const
return m;
}
QTextLineItemIterator::QTextLineItemIterator(QTextEngine *_eng, int _lineNum, const QPointF &pos,
const QTextLayout::FormatRange *_selection)
: eng(_eng),
line(eng->lines[_lineNum]),
si(0),
lineNum(_lineNum),
lineEnd(line.from + line.length),
firstItem(eng->findItem(line.from)),
lastItem(eng->findItem(lineEnd - 1)),
nItems((firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0),
logicalItem(-1),
item(-1),
visualOrder(nItems),
levels(nItems),
selection(_selection)
{
pos_x = x = QFixed::fromReal(pos.x());
x += line.x;
x += eng->alignLine(line);
for (int i = 0; i < nItems; ++i)
levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
eng->shapeLine(line);
}
QScriptItem &QTextLineItemIterator::next()
{
x += itemWidth;
++logicalItem;
item = visualOrder[logicalItem] + firstItem;
itemLength = eng->length(item);
si = &eng->layoutData->items[item];
if (!si->num_glyphs)
eng->shape(item);
if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
itemWidth = si->width;
return *si;
}
unsigned short *logClusters = eng->logClusters(si);
QGlyphLayout glyphs = eng->shapedGlyphs(si);
itemStart = qMax(line.from, si->position);
glyphsStart = logClusters[itemStart - si->position];
if (lineEnd < si->position + itemLength) {
itemEnd = lineEnd;
glyphsEnd = logClusters[itemEnd-si->position];
} else {
itemEnd = si->position + itemLength;
glyphsEnd = si->num_glyphs;
}
// show soft-hyphen at line-break
if (si->position + itemLength >= lineEnd
&& eng->layoutData->string.at(lineEnd - 1) == 0x00ad)
glyphs.attributes[glyphsEnd - 1].dontPrint = false;
itemWidth = 0;
for (int g = glyphsStart; g < glyphsEnd; ++g)
itemWidth += glyphs.effectiveAdvance(g);
return *si;
}
bool QTextLineItemIterator::getSelectionBounds(QFixed *selectionX, QFixed *selectionWidth) const
{
*selectionX = *selectionWidth = 0;
if (!selection)
return false;
if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
if (si->position >= selection->start + selection->length
|| si->position + itemLength <= selection->start)
return false;
*selectionX = x;
*selectionWidth = itemWidth;
} else {
unsigned short *logClusters = eng->logClusters(si);
QGlyphLayout glyphs = eng->shapedGlyphs(si);
int from = qMax(itemStart, selection->start) - si->position;
int to = qMin(itemEnd, selection->start + selection->length) - si->position;
if (from >= to)
return false;
int start_glyph = logClusters[from];
int end_glyph = (to == eng->length(item)) ? si->num_glyphs : logClusters[to];
QFixed soff;
QFixed swidth;
if (si->analysis.bidiLevel %2) {
for (int g = glyphsEnd - 1; g >= end_glyph; --g)
soff += glyphs.effectiveAdvance(g);
for (int g = end_glyph - 1; g >= start_glyph; --g)
swidth += glyphs.effectiveAdvance(g);
} else {
for (int g = glyphsStart; g < start_glyph; ++g)
soff += glyphs.effectiveAdvance(g);
for (int g = start_glyph; g < end_glyph; ++g)
swidth += glyphs.effectiveAdvance(g);
}
// If the starting character is in the middle of a ligature,
// selection should only contain the right part of that ligature
// glyph, so we need to get the width of the left part here and
// add it to *selectionX
QFixed leftOffsetInLigature = eng->offsetInLigature(si, from, to, start_glyph);
*selectionX = x + soff + leftOffsetInLigature;
*selectionWidth = swidth - leftOffsetInLigature;
// If the ending character is also part of a ligature, swidth does
// not contain that part yet, we also need to find out the width of
// that left part
*selectionWidth += eng->offsetInLigature(si, to, eng->length(item), end_glyph);
}
return true;
}
QT_END_NAMESPACE

View File

@ -64,6 +64,7 @@
#include "QtGui/qpaintengine.h"
#include "QtGui/qtextobject.h"
#include "QtGui/qtextoption.h"
#include "QtGui/qtextcursor.h"
#include "QtCore/qset.h"
#include "QtCore/qdebug.h"
#ifndef QT_BUILD_COMPAT_LIB
@ -471,6 +472,7 @@ public:
void shape(int item) const;
void justify(const QScriptLine &si);
QFixed alignLine(const QScriptLine &line);
QFixed width(int charFrom, int numChars) const;
glyph_metrics_t boundingBox(int from, int len) const;
@ -586,12 +588,18 @@ public:
uint cacheGlyphs : 1;
uint stackEngine : 1;
uint forceJustification : 1;
uint visualMovement : 1;
int *underlinePositions;
mutable LayoutData *layoutData;
inline bool hasFormats() const { return (block.docHandle() || specialData); }
inline bool visualCursorMovement() const
{
return (visualMovement ||
(block.docHandle() ? block.docHandle()->defaultCursorMoveStyle == QTextCursor::Visual : false));
}
struct SpecialData {
int preeditPosition;
@ -611,6 +619,13 @@ public:
void shapeLine(const QScriptLine &line);
QFixed leadingSpaceWidth(const QScriptLine &line);
QFixed offsetInLigature(const QScriptItem *si, int pos, int max, int glyph_pos);
int previousLogicalPosition(int oldPos) const;
int nextLogicalPosition(int oldPos) const;
int lineNumberForTextPosition(int pos);
int positionAfterVisualMovement(int oldPos, QTextCursor::MoveOperation op);
void insertionPointsForLine(int lineNum, QVector<int> &insertionPoints);
private:
void setBoundary(int strPos) const;
void addRequiredBoundaries() const;
@ -625,6 +640,8 @@ private:
void splitItem(int item, int pos) const;
void resolveAdditionalFormats() const;
int endOfLine(int lineNum);
int beginningOfLine(int lineNum);
};
class QStackTextEngine : public QTextEngine {
@ -635,6 +652,49 @@ public:
void *_memory[MemSize];
};
struct QTextLineItemIterator
{
QTextLineItemIterator(QTextEngine *eng, int lineNum, const QPointF &pos = QPointF(),
const QTextLayout::FormatRange *_selection = 0);
inline bool atEnd() const { return logicalItem >= nItems - 1; }
inline bool atBeginning() const { return logicalItem <= 0; }
QScriptItem &next();
bool getSelectionBounds(QFixed *selectionX, QFixed *selectionWidth) const;
inline bool isOutsideSelection() const {
QFixed tmp1, tmp2;
return !getSelectionBounds(&tmp1, &tmp2);
}
QTextEngine *eng;
QFixed x;
QFixed pos_x;
const QScriptLine &line;
QScriptItem *si;
int lineNum;
int lineEnd;
int firstItem;
int lastItem;
int nItems;
int logicalItem;
int item;
int itemLength;
int glyphsStart;
int glyphsEnd;
int itemStart;
int itemEnd;
QFixed itemWidth;
QVarLengthArray<int> visualOrder;
QVarLengthArray<uchar> levels;
const QTextLayout::FormatRange *selection;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QTextEngine::ShaperFlags)

View File

@ -72,23 +72,6 @@ QT_BEGIN_NAMESPACE
#define SuppressText 0x5012
#define SuppressBackground 0x513
static QFixed alignLine(QTextEngine *eng, const QScriptLine &line)
{
QFixed x = 0;
eng->justify(line);
// if width is QFIXED_MAX that means we used setNumColumns() and that implicitly makes this line left aligned.
if (!line.justified && line.width != QFIXED_MAX) {
int align = eng->option.alignment();
if (align & Qt::AlignJustify && eng->isRightToLeft())
align = Qt::AlignRight;
if (align & Qt::AlignRight)
x = line.width - (line.textAdvance + eng->leadingSpaceWidth(line));
else if (align & Qt::AlignHCenter)
x = (line.width - line.textAdvance)/2;
}
return x;
}
/*!
\class QTextLayout::FormatRange
\reentrant
@ -595,6 +578,30 @@ bool QTextLayout::cacheEnabled() const
return d->cacheGlyphs;
}
/*!
Set the visual cursor movement style. If the QTextLayout is backed by
a document, you can ignore this and use the option in QTextDocument,
this option is for widgets like QLineEdit or custom widgets without
a QTextDocument. Default value is QTextCursor::Logical.
\sa setCursorMoveStyle()
*/
void QTextLayout::setCursorMoveStyle(QTextCursor::MoveStyle style)
{
d->visualMovement = style == QTextCursor::Visual ? true : false;
}
/*!
The cursor movement style of this QTextLayout. The default is
QTextCursor::Logical.
\sa setCursorMoveStyle()
*/
QTextCursor::MoveStyle QTextLayout::cursorMoveStyle() const
{
return d->visualMovement ? QTextCursor::Visual : QTextCursor::Logical;
}
/*!
Begins the layout process.
@ -718,6 +725,34 @@ int QTextLayout::previousCursorPosition(int oldPos, CursorMode mode) const
}
/*!
Returns the cursor position to the right of \a oldPos, next to it.
It's dependent on the visual position of characters, after bi-directional
reordering.
\sa leftCursorPosition(), nextCursorPosition()
*/
int QTextLayout::rightCursorPosition(int oldPos) const
{
int newPos = d->positionAfterVisualMovement(oldPos, QTextCursor::Right);
// qDebug("%d -> %d", oldPos, newPos);
return newPos;
}
/*!
Returns the cursor position to the left of \a oldPos, next to it.
It's dependent on the visual position of characters, after bi-directional
reordering.
\sa rightCursorPosition(), previousCursorPosition()
*/
int QTextLayout::leftCursorPosition(int oldPos) const
{
int newPos = d->positionAfterVisualMovement(oldPos, QTextCursor::Left);
// qDebug("%d -> %d", oldPos, newPos);
return newPos;
}
/*!/
Returns true if position \a pos is a valid cursor position.
In a Unicode context some positions in the text are not valid
@ -815,16 +850,8 @@ QTextLine QTextLayout::lineAt(int i) const
*/
QTextLine QTextLayout::lineForTextPosition(int pos) const
{
for (int i = 0; i < d->lines.size(); ++i) {
const QScriptLine& line = d->lines[i];
if (line.from + (int)line.length > pos)
return QTextLine(i, d);
}
if (!d->layoutData)
d->itemize();
if (pos == d->layoutData->string.length() && d->lines.size())
return QTextLine(d->lines.size()-1, d);
return QTextLine();
int lineNum = d->lineNumberForTextPosition(pos);
return lineNum >= 0 ? lineAt(lineNum) : QTextLine();
}
/*!
@ -919,201 +946,6 @@ void QTextLayout::setFlags(int flags)
}
}
struct QTextLineItemIterator
{
QTextLineItemIterator(QTextEngine *eng, int lineNum, const QPointF &pos = QPointF(),
const QTextLayout::FormatRange *_selection = 0);
inline bool atEnd() const { return logicalItem >= nItems - 1; }
QScriptItem &next();
bool getSelectionBounds(QFixed *selectionX, QFixed *selectionWidth) const;
inline bool isOutsideSelection() const {
QFixed tmp1, tmp2;
return !getSelectionBounds(&tmp1, &tmp2);
}
QTextEngine *eng;
QFixed x;
QFixed pos_x;
const QScriptLine &line;
QScriptItem *si;
int lineEnd;
int firstItem;
int lastItem;
int nItems;
int logicalItem;
int item;
int itemLength;
int glyphsStart;
int glyphsEnd;
int itemStart;
int itemEnd;
QFixed itemWidth;
QVarLengthArray<int> visualOrder;
QVarLengthArray<uchar> levels;
const QTextLayout::FormatRange *selection;
};
QTextLineItemIterator::QTextLineItemIterator(QTextEngine *_eng, int lineNum, const QPointF &pos,
const QTextLayout::FormatRange *_selection)
: eng(_eng),
line(eng->lines[lineNum]),
si(0),
lineEnd(line.from + line.length),
firstItem(eng->findItem(line.from)),
lastItem(eng->findItem(lineEnd - 1)),
nItems((firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0),
logicalItem(-1),
item(-1),
visualOrder(nItems),
levels(nItems),
selection(_selection)
{
pos_x = x = QFixed::fromReal(pos.x());
x += line.x;
x += alignLine(eng, line);
for (int i = 0; i < nItems; ++i)
levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
eng->shapeLine(line);
}
QScriptItem &QTextLineItemIterator::next()
{
x += itemWidth;
++logicalItem;
item = visualOrder[logicalItem] + firstItem;
itemLength = eng->length(item);
si = &eng->layoutData->items[item];
if (!si->num_glyphs)
eng->shape(item);
if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
itemWidth = si->width;
return *si;
}
unsigned short *logClusters = eng->logClusters(si);
QGlyphLayout glyphs = eng->shapedGlyphs(si);
itemStart = qMax(line.from, si->position);
glyphsStart = logClusters[itemStart - si->position];
if (lineEnd < si->position + itemLength) {
itemEnd = lineEnd;
glyphsEnd = logClusters[itemEnd-si->position];
} else {
itemEnd = si->position + itemLength;
glyphsEnd = si->num_glyphs;
}
// show soft-hyphen at line-break
if (si->position + itemLength >= lineEnd
&& eng->layoutData->string.at(lineEnd - 1) == 0x00ad)
glyphs.attributes[glyphsEnd - 1].dontPrint = false;
itemWidth = 0;
for (int g = glyphsStart; g < glyphsEnd; ++g)
itemWidth += glyphs.effectiveAdvance(g);
return *si;
}
static QFixed offsetInLigature(const unsigned short *logClusters,
const QGlyphLayout &glyphs,
int pos, int max, int glyph_pos)
{
int offsetInCluster = 0;
for (int i = pos - 1; i >= 0; i--) {
if (logClusters[i] == glyph_pos)
offsetInCluster++;
else
break;
}
// in the case that the offset is inside a (multi-character) glyph,
// interpolate the position.
if (offsetInCluster > 0) {
int clusterLength = 0;
for (int i = pos - offsetInCluster; i < max; i++) {
if (logClusters[i] == glyph_pos)
clusterLength++;
else
break;
}
if (clusterLength)
return glyphs.advances_x[glyph_pos] * offsetInCluster / clusterLength;
}
return 0;
}
bool QTextLineItemIterator::getSelectionBounds(QFixed *selectionX, QFixed *selectionWidth) const
{
*selectionX = *selectionWidth = 0;
if (!selection)
return false;
if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
if (si->position >= selection->start + selection->length
|| si->position + itemLength <= selection->start)
return false;
*selectionX = x;
*selectionWidth = itemWidth;
} else {
unsigned short *logClusters = eng->logClusters(si);
QGlyphLayout glyphs = eng->shapedGlyphs(si);
int from = qMax(itemStart, selection->start) - si->position;
int to = qMin(itemEnd, selection->start + selection->length) - si->position;
if (from >= to)
return false;
int start_glyph = logClusters[from];
int end_glyph = (to == eng->length(item)) ? si->num_glyphs : logClusters[to];
QFixed soff;
QFixed swidth;
if (si->analysis.bidiLevel %2) {
for (int g = glyphsEnd - 1; g >= end_glyph; --g)
soff += glyphs.effectiveAdvance(g);
for (int g = end_glyph - 1; g >= start_glyph; --g)
swidth += glyphs.effectiveAdvance(g);
} else {
for (int g = glyphsStart; g < start_glyph; ++g)
soff += glyphs.effectiveAdvance(g);
for (int g = start_glyph; g < end_glyph; ++g)
swidth += glyphs.effectiveAdvance(g);
}
// If the starting character is in the middle of a ligature,
// selection should only contain the right part of that ligature
// glyph, so we need to get the width of the left part here and
// add it to *selectionX
QFixed leftOffsetInLigature = offsetInLigature(logClusters, glyphs, from,
to, start_glyph);
*selectionX = x + soff + leftOffsetInLigature;
*selectionWidth = swidth - leftOffsetInLigature;
// If the ending character is also part of a ligature, swidth does
// not contain that part yet, we also need to find out the width of
// that left part
*selectionWidth += offsetInLigature(logClusters, glyphs, to,
eng->length(item), end_glyph);
}
return true;
}
static void addSelectedRegionsToPath(QTextEngine *eng, int lineNumber, const QPointF &pos, QTextLayout::FormatRange *selection,
QPainterPath *region, QRectF boundingRect)
{
@ -1382,18 +1214,9 @@ void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition
QFixed pos_y = QFixed::fromReal(position.y());
cursorPosition = qBound(0, cursorPosition, d->layoutData->string.length());
int line = 0;
if (cursorPosition == d->layoutData->string.length()) {
line = d->lines.size() - 1;
} else {
// ### binary search
for (line = 0; line < d->lines.size(); line++) {
const QScriptLine &sl = d->lines[line];
if (sl.from <= cursorPosition && sl.from + (int)sl.length > cursorPosition)
break;
}
}
int line = d->lineNumberForTextPosition(cursorPosition);
if (line < 0)
line = 0;
if (line >= d->lines.size())
return;
@ -1402,7 +1225,15 @@ void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition
qreal x = position.x() + l.cursorToX(cursorPosition);
int itm = d->findItem(cursorPosition - 1);
int itm;
if (d->visualCursorMovement()) {
if (cursorPosition == sl.from + sl.length)
cursorPosition--;
itm = d->findItem(cursorPosition);
} else
itm = d->findItem(cursorPosition - 1);
QFixed base = sl.base();
QFixed descent = sl.descent;
bool rightToLeft = d->isRightToLeft();
@ -1512,7 +1343,7 @@ QRectF QTextLine::rect() const
QRectF QTextLine::naturalTextRect() const
{
const QScriptLine& sl = eng->lines[i];
QFixed x = sl.x + alignLine(eng, sl);
QFixed x = sl.x + eng->alignLine(sl);
QFixed width = sl.textWidth;
if (sl.justified)
@ -2635,9 +2466,10 @@ qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
eng->itemize();
const QScriptLine &line = eng->lines[i];
bool lastLine = i >= eng->lines.size() - 1;
QFixed x = line.x;
x += alignLine(eng, line);
x += eng->alignLine(line);
if (!i && !eng->layoutData->items.size()) {
*cursorPos = 0;
@ -2723,21 +2555,29 @@ qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
logClusters = eng->logClusters(si);
glyphs = eng->shapedGlyphs(si);
if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
if(pos == l)
if (pos == (reverse ? 0 : l))
x += si->width;
} else {
bool rtl = eng->isRightToLeft();
bool visual = eng->visualCursorMovement();
if (reverse) {
int end = qMin(lineEnd, si->position + l) - si->position;
int glyph_end = end == l ? si->num_glyphs : logClusters[end];
for (int i = glyph_end - 1; i >= glyph_pos; i--)
int glyph_start = glyph_pos;
if (visual && !rtl && !(lastLine && itm == (visualOrder[nItems - 1] + firstItem)))
glyph_start++;
for (int i = glyph_end - 1; i >= glyph_start; i--)
x += glyphs.effectiveAdvance(i);
} else {
int start = qMax(line.from - si->position, 0);
int glyph_start = logClusters[start];
for (int i = glyph_start; i < glyph_pos; i++)
int glyph_end = glyph_pos;
if (!visual || !rtl || (lastLine && itm == visualOrder[0] + firstItem))
glyph_end--;
for (int i = glyph_start; i <= glyph_end; i++)
x += glyphs.effectiveAdvance(i);
}
x += offsetInLigature(logClusters, glyphs, pos, line.length, glyph_pos);
x += eng->offsetInLigature(si, pos, line.length, glyph_pos);
}
*cursorPos = pos + si->position;
@ -2756,6 +2596,8 @@ int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
{
QFixed x = QFixed::fromReal(_x);
const QScriptLine &line = eng->lines[i];
bool lastLine = i >= eng->lines.size() - 1;
int lineNum = i;
if (!eng->layoutData)
eng->itemize();
@ -2773,7 +2615,7 @@ int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
return 0;
x -= line.x;
x -= alignLine(eng, line);
x -= eng->alignLine(line);
// qDebug("xToCursor: x=%f, cpos=%d", x.toReal(), cpos);
QVarLengthArray<int> visualOrder(nItems);
@ -2782,6 +2624,7 @@ int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
bool visual = eng->visualCursorMovement();
if (x <= 0) {
// left of first item
int item = visualOrder[0]+firstItem;
@ -2798,8 +2641,13 @@ int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
|| (line.justified && x < line.width)) {
// has to be in one of the runs
QFixed pos;
bool rtl = eng->isRightToLeft();
eng->shapeLine(line);
QVector<int> insertionPoints;
if (visual && rtl)
eng->insertionPointsForLine(lineNum, insertionPoints);
int nchars = 0;
for (int i = 0; i < nItems; ++i) {
int item = visualOrder[i]+firstItem;
QScriptItem &si = eng->layoutData->items[item];
@ -2829,6 +2677,7 @@ int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
if (pos + item_width < x) {
pos += item_width;
nchars += end;
continue;
}
// qDebug(" inside run");
@ -2873,27 +2722,60 @@ int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
} else {
QFixed dist = INT_MAX/256;
if (si.analysis.bidiLevel % 2) {
pos += item_width;
while (gs <= ge) {
if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
glyph_pos = gs;
dist = qAbs(x-pos);
if (!visual || rtl || (lastLine && i == nItems - 1)) {
pos += item_width;
while (gs <= ge) {
if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
glyph_pos = gs;
dist = qAbs(x-pos);
}
pos -= glyphs.effectiveAdvance(gs);
++gs;
}
} else {
while (ge >= gs) {
if (glyphs.attributes[ge].clusterStart && qAbs(x-pos) < dist) {
glyph_pos = ge;
dist = qAbs(x-pos);
}
pos += glyphs.effectiveAdvance(ge);
--ge;
}
pos -= glyphs.effectiveAdvance(gs);
++gs;
}
} else {
while (gs <= ge) {
if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
glyph_pos = gs;
dist = qAbs(x-pos);
if (!visual || !rtl || (lastLine && i == 0)) {
while (gs <= ge) {
if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
glyph_pos = gs;
dist = qAbs(x-pos);
}
pos += glyphs.effectiveAdvance(gs);
++gs;
}
pos += glyphs.effectiveAdvance(gs);
++gs;
} else {
QFixed oldPos = pos;
while (gs <= ge) {
pos += glyphs.effectiveAdvance(gs);
if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
glyph_pos = gs;
dist = qAbs(x-pos);
}
++gs;
}
pos = oldPos;
}
}
if (qAbs(x-pos) < dist)
if (qAbs(x-pos) < dist) {
if (visual) {
if (!rtl && i < nItems - 1) {
nchars += end;
continue;
}
if (rtl && nchars > 0)
return insertionPoints[lastLine ? nchars : nchars - 1];
}
return si.position + end;
}
}
Q_ASSERT(glyph_pos != -1);
int j;

View File

@ -50,6 +50,7 @@
#include <QtGui/qevent.h>
#include <QtGui/qtextformat.h>
#include <QtGui/qglyphs.h>
#include <QtGui/qtextcursor.h>
QT_BEGIN_HEADER
@ -136,6 +137,9 @@ public:
void setCacheEnabled(bool enable);
bool cacheEnabled() const;
void setCursorMoveStyle(QTextCursor::MoveStyle style);
QTextCursor::MoveStyle cursorMoveStyle() const;
void beginLayout();
void endLayout();
void clearLayout();
@ -153,6 +157,8 @@ public:
bool isValidCursorPosition(int pos) const;
int nextCursorPosition(int oldPos, CursorMode mode = SkipCharacters) const;
int previousCursorPosition(int oldPos, CursorMode mode = SkipCharacters) const;
int leftCursorPosition(int oldPos) const;
int rightCursorPosition(int oldPos) const;
void draw(QPainter *p, const QPointF &pos, const QVector<FormatRange> &selections = QVector<FormatRange>(),
const QRectF &clip = QRectF()) const;

View File

@ -1585,6 +1585,7 @@ void QLineControl::processKeyEvent(QKeyEvent* event)
}
bool unknown = false;
bool visual = cursorMoveStyle() == QTextCursor::Visual;
if (false) {
}
@ -1649,11 +1650,11 @@ void QLineControl::processKeyEvent(QKeyEvent* event)
#endif
moveCursor(selectionEnd(), false);
} else {
cursorForward(0, layoutDirection() == Qt::LeftToRight ? 1 : -1);
cursorForward(0, visual ? 1 : (layoutDirection() == Qt::LeftToRight ? 1 : -1));
}
}
else if (event == QKeySequence::SelectNextChar) {
cursorForward(1, layoutDirection() == Qt::LeftToRight ? 1 : -1);
cursorForward(1, visual ? 1 : (layoutDirection() == Qt::LeftToRight ? 1 : -1));
}
else if (event == QKeySequence::MoveToPreviousChar) {
#if !defined(Q_WS_WIN) || defined(QT_NO_COMPLETER)
@ -1664,11 +1665,11 @@ void QLineControl::processKeyEvent(QKeyEvent* event)
#endif
moveCursor(selectionStart(), false);
} else {
cursorForward(0, layoutDirection() == Qt::LeftToRight ? -1 : 1);
cursorForward(0, visual ? -1 : (layoutDirection() == Qt::LeftToRight ? -1 : 1));
}
}
else if (event == QKeySequence::SelectPreviousChar) {
cursorForward(1, layoutDirection() == Qt::LeftToRight ? -1 : 1);
cursorForward(1, visual ? -1 : (layoutDirection() == Qt::LeftToRight ? -1 : 1));
}
else if (event == QKeySequence::MoveToNextWord) {
if (echoMode() == QLineEdit::Normal)

View File

@ -160,6 +160,8 @@ public:
int cursorWidth() const { return m_cursorWidth; }
void setCursorWidth(int value) { m_cursorWidth = value; }
QTextCursor::MoveStyle cursorMoveStyle() const { return m_textLayout.cursorMoveStyle(); }
void setCursorMoveStyle(QTextCursor::MoveStyle style) { m_textLayout.setCursorMoveStyle(style); }
void moveCursor(int pos, bool mark = false);
void cursorForward(bool mark, int steps)
@ -167,10 +169,12 @@ public:
int c = m_cursor;
if (steps > 0) {
while (steps--)
c = m_textLayout.nextCursorPosition(c);
c = cursorMoveStyle() == QTextCursor::Visual ? m_textLayout.rightCursorPosition(c)
: m_textLayout.nextCursorPosition(c);
} else if (steps < 0) {
while (steps++)
c = m_textLayout.previousCursorPosition(c);
c = cursorMoveStyle() == QTextCursor::Visual ? m_textLayout.leftCursorPosition(c)
: m_textLayout.previousCursorPosition(c);
}
moveCursor(c, mark);
}

View File

@ -1111,6 +1111,34 @@ void QLineEdit::setDragEnabled(bool b)
}
/*!
\property QLineEdit::cursorMoveStyle
\brief the movement style of cursor in this line edit
\since 4.8
When this property is set to QTextCursor::Visual, the line edit will use visual
movement style. Pressing the left arrow key will always cause the cursor to move
left, regardless of the text's writing direction. The same behavior applies to
right arrow key.
When the property is QTextCursor::Logical (the default), within a LTR text block,
increase cursor position when pressing left arrow key, decrease cursor position
when pressing the right arrow key. If the text block is right to left, the opposite
behavior applies.
*/
QTextCursor::MoveStyle QLineEdit::cursorMoveStyle() const
{
Q_D(const QLineEdit);
return d->control->cursorMoveStyle();
}
void QLineEdit::setCursorMoveStyle(QTextCursor::MoveStyle style)
{
Q_D(QLineEdit);
d->control->setCursorMoveStyle(style);
}
/*!
\property QLineEdit::acceptableInput
\brief whether the input satisfies the inputMask and the

View File

@ -43,6 +43,7 @@
#define QLINEEDIT_H
#include <QtGui/qframe.h>
#include <QtGui/qtextcursor.h>
#include <QtCore/qstring.h>
#include <QtCore/qmargins.h>
@ -158,6 +159,9 @@ public:
void setDragEnabled(bool b);
bool dragEnabled() const;
void setCursorMoveStyle(QTextCursor::MoveStyle style);
QTextCursor::MoveStyle cursorMoveStyle() const;
QString inputMask() const;
void setInputMask(const QString &inputMask);
bool hasAcceptableInput() const;

View File

@ -71,6 +71,10 @@ private slots:
void bidiReorderString();
void bidiCursor_qtbug2795();
void bidiCursor_PDF();
void bidiCursorMovement_data();
void bidiCursorMovement();
void bidiCursorLogicalMovement_data();
void bidiCursorLogicalMovement();
};
tst_QComplexText::tst_QComplexText()
@ -185,6 +189,89 @@ void tst_QComplexText::bidiCursor_qtbug2795()
QVERIFY(x1 == x2);
}
void tst_QComplexText::bidiCursorMovement_data()
{
QTest::addColumn<QString>("logical");
QTest::addColumn<int>("basicDir");
const LV *data = logical_visual;
while ( data->name ) {
//next we fill it with data
QTest::newRow( data->name )
<< QString::fromUtf8( data->logical )
<< (int) data->basicDir;
data++;
}
}
void tst_QComplexText::bidiCursorMovement()
{
QFETCH(QString, logical);
QFETCH(int, basicDir);
QTextLayout layout(logical);
QTextOption option = layout.textOption();
option.setTextDirection(basicDir == QChar::DirL ? Qt::LeftToRight : Qt::RightToLeft);
layout.setTextOption(option);
bool moved;
int oldPos, newPos = 0;
qreal x, newX;
layout.beginLayout();
QTextLine line = layout.createLine();
layout.endLayout();
newX = line.cursorToX(0);
do {
oldPos = newPos;
x = newX;
newX = line.cursorToX(oldPos);
if (basicDir == QChar::DirL) {
QVERIFY(newX >= x);
newPos = layout.rightCursorPosition(oldPos);
} else
{
QVERIFY(newX <= x);
newPos = layout.leftCursorPosition(oldPos);
}
moved = (oldPos != newPos);
} while (moved);
}
void tst_QComplexText::bidiCursorLogicalMovement_data()
{
bidiCursorMovement_data();
}
void tst_QComplexText::bidiCursorLogicalMovement()
{
QFETCH(QString, logical);
QFETCH(int, basicDir);
QTextLayout layout(logical);
QTextOption option = layout.textOption();
option.setTextDirection(basicDir == QChar::DirL ? Qt::LeftToRight : Qt::RightToLeft);
layout.setTextOption(option);
bool moved;
int oldPos, newPos = 0;
do {
oldPos = newPos;
newPos = layout.nextCursorPosition(oldPos);
QVERIFY(newPos >= oldPos);
moved = (oldPos != newPos);
} while (moved);
do {
oldPos = newPos;
newPos = layout.previousCursorPosition(oldPos);
QVERIFY(newPos <= oldPos);
moved = (oldPos != newPos);
} while (moved);
}
void tst_QComplexText::bidiCursor_PDF()
{
QString str = QString::fromUtf8("\342\200\252hello\342\200\254");

View File

@ -282,6 +282,12 @@ private slots:
void validateAndSet();
#endif
void bidiVisualMovement_data();
void bidiVisualMovement();
void bidiLogicalMovement_data();
void bidiLogicalMovement();
protected slots:
#ifdef QT3_SUPPORT
void lostFocus();
@ -3760,5 +3766,135 @@ void tst_QLineEdit::QTBUG13520_textNotVisible()
}
void tst_QLineEdit::bidiVisualMovement_data()
{
QTest::addColumn<QString>("logical");
QTest::addColumn<int>("basicDir");
QTest::addColumn<IntList>("positionList");
QTest::newRow("Latin text")
<< QString::fromUtf8("abc")
<< (int) QChar::DirL
<< (IntList() << 0 << 1 << 2 << 3);
QTest::newRow("Hebrew text, one item")
<< QString::fromUtf8("\327\220\327\221\327\222")
<< (int) QChar::DirR
<< (QList<int>() << 0 << 1 << 2 << 3);
QTest::newRow("Hebrew text after Latin text")
<< QString::fromUtf8("abc\327\220\327\221\327\222")
<< (int) QChar::DirL
<< (QList<int>() << 0 << 1 << 2 << 6 << 5 << 4 << 3);
QTest::newRow("Latin text after Hebrew text")
<< QString::fromUtf8("\327\220\327\221\327\222abc")
<< (int) QChar::DirR
<< (QList<int>() << 0 << 1 << 2 << 6 << 5 << 4 << 3);
QTest::newRow("LTR, 3 items")
<< QString::fromUtf8("abc\327\220\327\221\327\222abc")
<< (int) QChar::DirL
<< (QList<int>() << 0 << 1 << 2 << 5 << 4 << 3 << 6 << 7 << 8 << 9);
QTest::newRow("RTL, 3 items")
<< QString::fromUtf8("\327\220\327\221\327\222abc\327\220\327\221\327\222")
<< (int) QChar::DirR
<< (QList<int>() << 0 << 1 << 2 << 5 << 4 << 3 << 6 << 7 << 8 << 9);
QTest::newRow("LTR, 4 items")
<< QString::fromUtf8("abc\327\220\327\221\327\222abc\327\220\327\221\327\222")
<< (int) QChar::DirL
<< (QList<int>() << 0 << 1 << 2 << 5 << 4 << 3 << 6 << 7 << 8 << 12 << 11 << 10 << 9);
QTest::newRow("RTL, 4 items")
<< QString::fromUtf8("\327\220\327\221\327\222abc\327\220\327\221\327\222abc")
<< (int) QChar::DirR
<< (QList<int>() << 0 << 1 << 2 << 5 << 4 << 3 << 6 << 7 << 8 << 12 << 11 << 10 << 9);
}
void tst_QLineEdit::bidiVisualMovement()
{
QFETCH(QString, logical);
QFETCH(int, basicDir);
QFETCH(IntList, positionList);
QLineEdit le;
le.setText(logical);
le.setCursorMoveStyle(QTextCursor::Visual);
le.setCursorPosition(0);
bool moved;
int i = 0, oldPos, newPos = 0;
do {
oldPos = newPos;
QVERIFY(oldPos == positionList[i]);
if (basicDir == QChar::DirL) {
QTest::keyClick(&le, Qt::Key_Right);
} else
QTest::keyClick(&le, Qt::Key_Left);
newPos = le.cursorPosition();
moved = (oldPos != newPos);
i++;
} while (moved);
QVERIFY(i == positionList.size());
do {
i--;
oldPos = newPos;
QVERIFY(oldPos == positionList[i]);
if (basicDir == QChar::DirL) {
QTest::keyClick(&le, Qt::Key_Left);
} else
{
QTest::keyClick(&le, Qt::Key_Right);
}
newPos = le.cursorPosition();
moved = (oldPos != newPos);
} while (moved && i >= 0);
}
void tst_QLineEdit::bidiLogicalMovement_data()
{
bidiVisualMovement_data();
}
void tst_QLineEdit::bidiLogicalMovement()
{
QFETCH(QString, logical);
QFETCH(int, basicDir);
QLineEdit le;
le.setText(logical);
le.setCursorMoveStyle(QTextCursor::Logical);
le.setCursorPosition(0);
bool moved;
int i = 0, oldPos, newPos = 0;
do {
oldPos = newPos;
QVERIFY(oldPos == i);
if (basicDir == QChar::DirL) {
QTest::keyClick(&le, Qt::Key_Right);
} else
QTest::keyClick(&le, Qt::Key_Left);
newPos = le.cursorPosition();
moved = (oldPos != newPos);
i++;
} while (moved);
do {
i--;
oldPos = newPos;
QVERIFY(oldPos == i);
if (basicDir == QChar::DirL) {
QTest::keyClick(&le, Qt::Key_Left);
} else
{
QTest::keyClick(&le, Qt::Key_Right);
}
newPos = le.cursorPosition();
moved = (oldPos != newPos);
} while (moved && i >= 0);
}
QTEST_MAIN(tst_QLineEdit)
#include "tst_qlineedit.moc"

View File

@ -42,7 +42,6 @@
#include <QtTest/QtTest>
#include <qtextedit.h>
#include <qtextcursor.h>
#include <qtextlist.h>
@ -69,6 +68,7 @@ typedef QList<keyPairType> pairListType;
Q_DECLARE_METATYPE(pairListType);
Q_DECLARE_METATYPE(keyPairType);
Q_DECLARE_METATYPE(QList<bool>);
Q_DECLARE_METATYPE(QList<int>);
#ifdef Q_WS_MAC
#include <Carbon/Carbon.h>
@ -205,6 +205,11 @@ private slots:
#ifndef QT_NO_CONTEXTMENU
void taskQTBUG_7902_contextMenuCrash();
#endif
void bidiVisualMovement_data();
void bidiVisualMovement();
void bidiLogicalMovement_data();
void bidiLogicalMovement();
private:
void createSelection();
@ -2235,5 +2240,147 @@ void tst_QTextEdit::taskQTBUG_7902_contextMenuCrash()
}
#endif
void tst_QTextEdit::bidiVisualMovement_data()
{
QTest::addColumn<QString>("logical");
QTest::addColumn<int>("basicDir");
QTest::addColumn<QList<int> >("positionList");
QTest::newRow("Latin text")
<< QString::fromUtf8("abc")
<< (int) QChar::DirL
<< (QList<int>() << 0 << 1 << 2 << 3);
QTest::newRow("Hebrew text, one item")
<< QString::fromUtf8("\327\220\327\221\327\222")
<< (int) QChar::DirR
<< (QList<int>() << 0 << 1 << 2 << 3);
QTest::newRow("Hebrew text after Latin text")
<< QString::fromUtf8("abc\327\220\327\221\327\222")
<< (int) QChar::DirL
<< (QList<int>() << 0 << 1 << 2 << 6 << 5 << 4 << 3);
QTest::newRow("Latin text after Hebrew text")
<< QString::fromUtf8("\327\220\327\221\327\222abc")
<< (int) QChar::DirR
<< (QList<int>() << 0 << 1 << 2 << 6 << 5 << 4 << 3);
QTest::newRow("LTR, 3 items")
<< QString::fromUtf8("abc\327\220\327\221\327\222abc")
<< (int) QChar::DirL
<< (QList<int>() << 0 << 1 << 2 << 5 << 4 << 3 << 6 << 7 << 8 << 9);
QTest::newRow("RTL, 3 items")
<< QString::fromUtf8("\327\220\327\221\327\222abc\327\220\327\221\327\222")
<< (int) QChar::DirR
<< (QList<int>() << 0 << 1 << 2 << 5 << 4 << 3 << 6 << 7 << 8 << 9);
QTest::newRow("LTR, 4 items")
<< QString::fromUtf8("abc\327\220\327\221\327\222abc\327\220\327\221\327\222")
<< (int) QChar::DirL
<< (QList<int>() << 0 << 1 << 2 << 5 << 4 << 3 << 6 << 7 << 8 << 12 << 11 << 10 << 9);
QTest::newRow("RTL, 4 items")
<< QString::fromUtf8("\327\220\327\221\327\222abc\327\220\327\221\327\222abc")
<< (int) QChar::DirR
<< (QList<int>() << 0 << 1 << 2 << 5 << 4 << 3 << 6 << 7 << 8 << 12 << 11 << 10 << 9);
}
void tst_QTextEdit::bidiVisualMovement()
{
QFETCH(QString, logical);
QFETCH(int, basicDir);
QFETCH(QList<int>, positionList);
ed->setText(logical);
QTextOption option = ed->document()->defaultTextOption();
option.setTextDirection(basicDir == QChar::DirL ? Qt::LeftToRight : Qt::RightToLeft);
ed->document()->setDefaultTextOption(option);
ed->document()->setDefaultCursorMoveStyle(QTextCursor::Visual);
ed->moveCursor(QTextCursor::Start);
ed->show();
bool moved;
int i = 0, oldPos, newPos = 0;
do {
oldPos = newPos;
QVERIFY(oldPos == positionList[i]);
if (basicDir == QChar::DirL) {
ed->moveCursor(QTextCursor::Right);
} else
{
ed->moveCursor(QTextCursor::Left);
}
newPos = ed->textCursor().position();
moved = (oldPos != newPos);
i++;
} while (moved);
QVERIFY(i == positionList.size());
do {
i--;
oldPos = newPos;
QVERIFY(oldPos == positionList[i]);
if (basicDir == QChar::DirL) {
ed->moveCursor(QTextCursor::Left);
} else
{
ed->moveCursor(QTextCursor::Right);
}
newPos = ed->textCursor().position();
moved = (oldPos != newPos);
} while (moved && i >= 0);
}
void tst_QTextEdit::bidiLogicalMovement_data()
{
bidiVisualMovement_data();
}
void tst_QTextEdit::bidiLogicalMovement()
{
QFETCH(QString, logical);
QFETCH(int, basicDir);
ed->setText(logical);
QTextOption option = ed->document()->defaultTextOption();
option.setTextDirection(basicDir == QChar::DirL ? Qt::LeftToRight : Qt::RightToLeft);
ed->document()->setDefaultTextOption(option);
ed->document()->setDefaultCursorMoveStyle(QTextCursor::Logical);
ed->moveCursor(QTextCursor::Start);
ed->show();
bool moved;
int i = 0, oldPos, newPos = 0;
do {
oldPos = newPos;
QVERIFY(oldPos == i);
if (basicDir == QChar::DirL) {
ed->moveCursor(QTextCursor::Right);
} else
{
ed->moveCursor(QTextCursor::Left);
}
newPos = ed->textCursor().position();
moved = (oldPos != newPos);
i++;
} while (moved);
do {
i--;
oldPos = newPos;
QVERIFY(oldPos == i);
if (basicDir == QChar::DirL) {
ed->moveCursor(QTextCursor::Left);
} else
{
ed->moveCursor(QTextCursor::Right);
}
newPos = ed->textCursor().position();
moved = (oldPos != newPos);
} while (moved && i >= 0);
}
QTEST_MAIN(tst_QTextEdit)
#include "tst_qtextedit.moc"