Fix char format of preedit text in empty text block

When a text block is empty, and we are adding preedit text to it,
we need to merge the format of the preedit text with the current
format of the cursor, otherwise we will use a default format and
then suddenly switch to the proper one when the text is committed.

The reason this becomes a bit complex is that there are no rules
preventing someone from using several ime attributes to specify
formats for isolated parts of the text, and no rules defining the
order of such attributes. So even if the common case is one
text format attribute for the entire string, we need to make sure
we also handle the other cases gracefully, e.g. when we are setting
different formats for different substrings and then providing these
out of order. To make sure we have these corner cases covered, we
also add a set of autotests.

[ChangeLog][Qt Widgets][TextEdit] Fixed initial char format of
input method text as it is in pre-edit mode.

Task-number: QTBUG-59196
Change-Id: I1e37928e3bd1395fec1b5591908d4c69b84eb618
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
This commit is contained in:
Eskil Abrahamsen Blomfeldt 2017-03-14 15:00:13 +01:00
parent ac74abdf50
commit 391e3aeef4
2 changed files with 281 additions and 2 deletions

View File

@ -2052,16 +2052,59 @@ void QWidgetTextControlPrivate::inputMethodEvent(QInputMethodEvent *e)
preeditCursor = a.start;
hideCursor = !a.length;
} else if (a.type == QInputMethodEvent::TextFormat) {
QTextCharFormat f = qvariant_cast<QTextFormat>(a.value).toCharFormat();
QTextCharFormat f = cursor.charFormat();
f.merge(qvariant_cast<QTextFormat>(a.value).toCharFormat());
if (f.isValid()) {
QTextLayout::FormatRange o;
o.start = a.start + cursor.position() - block.position();
o.length = a.length;
o.format = f;
overrides.append(o);
// Make sure list is sorted by start index
QVector<QTextLayout::FormatRange>::iterator it = overrides.end();
while (it != overrides.begin()) {
QVector<QTextLayout::FormatRange>::iterator previous = it - 1;
if (o.start >= previous->start) {
overrides.insert(it, o);
break;
}
it = previous;
}
if (it == overrides.begin())
overrides.prepend(o);
}
}
}
if (cursor.charFormat().isValid()) {
int start = cursor.position() - block.position();
int end = start + e->preeditString().length();
QVector<QTextLayout::FormatRange>::iterator it = overrides.begin();
while (it != overrides.end()) {
QTextLayout::FormatRange range = *it;
int rangeStart = range.start;
if (rangeStart > start) {
QTextLayout::FormatRange o;
o.start = start;
o.length = rangeStart - start;
o.format = cursor.charFormat();
it = overrides.insert(it, o) + 1;
}
++it;
start = range.start + range.length;
}
if (start < end) {
QTextLayout::FormatRange o;
o.start = start;
o.length = end - start;
o.format = cursor.charFormat();
overrides.append(o);
}
}
layout->setFormats(overrides);
cursor.endEditBlock();

View File

@ -46,6 +46,7 @@
#include <qcommonstyle.h>
#include <qlayout.h>
#include <qdir.h>
#include <qpaintengine.h>
#include <qabstracttextdocumentlayout.h>
#include <qtextdocumentfragment.h>
@ -60,6 +61,8 @@ typedef QPair<Qt::Key, Qt::KeyboardModifier> keyPairType;
typedef QList<keyPairType> pairListType;
Q_DECLARE_METATYPE(keyPairType);
Q_DECLARE_METATYPE(QList<QInputMethodEvent::Attribute>);
QT_FORWARD_DECLARE_CLASS(QTextEdit)
class tst_QTextEdit : public QObject
@ -200,6 +203,9 @@ private slots:
void wheelEvent();
#endif
void preeditCharFormat_data();
void preeditCharFormat();
private:
void createSelection();
int blockCount() const;
@ -2594,5 +2600,235 @@ void tst_QTextEdit::wheelEvent()
#endif
namespace {
class MyPaintEngine : public QPaintEngine
{
public:
bool begin(QPaintDevice *)
{
return true;
}
bool end()
{
return true;
}
void updateState(const QPaintEngineState &)
{
}
void drawPixmap(const QRectF &, const QPixmap &, const QRectF &)
{
}
void drawTextItem(const QPointF &, const QTextItem &textItem) Q_DECL_OVERRIDE
{
itemFonts.append(qMakePair(textItem.text(), textItem.font()));
}
Type type() const { return User; }
QList<QPair<QString, QFont> > itemFonts;
};
class MyPaintDevice : public QPaintDevice
{
public:
MyPaintDevice() : m_paintEngine(new MyPaintEngine)
{
}
QPaintEngine *paintEngine () const
{
return m_paintEngine;
}
int metric (QPaintDevice::PaintDeviceMetric metric) const {
switch (metric) {
case QPaintDevice::PdmWidth:
case QPaintDevice::PdmHeight:
case QPaintDevice::PdmWidthMM:
case QPaintDevice::PdmHeightMM:
case QPaintDevice::PdmNumColors:
return INT_MAX;
case QPaintDevice::PdmDepth:
return 32;
case QPaintDevice::PdmDpiX:
case QPaintDevice::PdmDpiY:
case QPaintDevice::PdmPhysicalDpiX:
case QPaintDevice::PdmPhysicalDpiY:
return 72;
case QPaintDevice::PdmDevicePixelRatio:
case QPaintDevice::PdmDevicePixelRatioScaled:
; // fall through
}
return 0;
}
MyPaintEngine *m_paintEngine;
};
}
void tst_QTextEdit::preeditCharFormat_data()
{
QTest::addColumn<QList<QInputMethodEvent::Attribute> >("imeAttributes");
QTest::addColumn<QStringList>("substrings");
QTest::addColumn<QList<bool> >("boldnessList");
QTest::addColumn<QList<bool> >("italicnessList");
QTest::addColumn<QList<int> >("pointSizeList");
{
QList<QInputMethodEvent::Attribute> attributes;
{
QTextCharFormat tcf;
tcf.setFontPointSize(13);
tcf.setFontItalic(true);
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 1, 1, tcf));
}
{
QTextCharFormat tcf;
tcf.setFontPointSize(8);
tcf.setFontWeight(QFont::Normal);
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 4, 2, tcf));
}
QTest::newRow("Two formats, middle, in order")
<< attributes
<< (QStringList() << "P" << "r" << "eE" << "di" << "tText")
<< (QList<bool>() << true << true << true << false << true)
<< (QList<bool>() << false << true << false << false << false)
<< (QList<int>() << 20 << 13 << 20 << 8 << 20);
}
{
QList<QInputMethodEvent::Attribute> attributes;
{
QTextCharFormat tcf;
tcf.setFontPointSize(8);
tcf.setFontWeight(QFont::Normal);
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 4, 2, tcf));
}
{
QTextCharFormat tcf;
tcf.setFontPointSize(13);
tcf.setFontItalic(true);
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 1, 1, tcf));
}
QTest::newRow("Two formats, middle, out of order")
<< attributes
<< (QStringList() << "P" << "r" << "eE" << "di" << "tText")
<< (QList<bool>() << true << true << true << false << true)
<< (QList<bool>() << false << true << false << false << false)
<< (QList<int>() << 20 << 13 << 20 << 8 << 20);
}
{
QList<QInputMethodEvent::Attribute> attributes;
{
QTextCharFormat tcf;
tcf.setFontPointSize(13);
tcf.setFontItalic(true);
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, 1, tcf));
}
{
QTextCharFormat tcf;
tcf.setFontPointSize(8);
tcf.setFontWeight(QFont::Normal);
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 4, 2, tcf));
}
QTest::newRow("Two formats, front, in order")
<< attributes
<< (QStringList() << "P" << "reE" << "di" << "tText")
<< (QList<bool>() << true << true << false << true)
<< (QList<bool>() << true << false << false << false)
<< (QList<int>() << 13 << 20 << 8 << 20);
}
{
QList<QInputMethodEvent::Attribute> attributes;
{
QTextCharFormat tcf;
tcf.setFontPointSize(8);
tcf.setFontWeight(QFont::Normal);
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 4, 2, tcf));
}
{
QTextCharFormat tcf;
tcf.setFontPointSize(13);
tcf.setFontItalic(true);
attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, 1, tcf));
}
QTest::newRow("Two formats, front, out of order")
<< attributes
<< (QStringList() << "P" << "reE" << "di" << "tText")
<< (QList<bool>() << true << true << false << true)
<< (QList<bool>() << true << false << false << false)
<< (QList<int>() << 13 << 20 << 8 << 20);
}
}
void tst_QTextEdit::preeditCharFormat()
{
QFETCH(QList<QInputMethodEvent::Attribute>, imeAttributes);
QFETCH(QStringList, substrings);
QFETCH(QList<bool>, boldnessList);
QFETCH(QList<bool>, italicnessList);
QFETCH(QList<int>, pointSizeList);
QTextEdit *w = new QTextEdit;
w->show();
QVERIFY(QTest::qWaitForWindowExposed(w));
// Set main char format
{
QTextCharFormat tcf;
tcf.setFontPointSize(20);
tcf.setFontWeight(QFont::Bold);
w->mergeCurrentCharFormat(tcf);
}
QList<QInputMethodEvent::Attribute> attributes;
attributes.prepend(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor,
w->textCursor().position(),
0,
QVariant()));
attributes += imeAttributes;
QInputMethodEvent event("PreEditText", attributes);
QApplication::sendEvent(w, &event);
MyPaintDevice device;
{
QPainter p(&device);
w->document()->drawContents(&p);
}
QCOMPARE(device.m_paintEngine->itemFonts.size(), substrings.size());
for (int i = 0; i < substrings.size(); ++i)
QCOMPARE(device.m_paintEngine->itemFonts.at(i).first, substrings.at(i));
for (int i = 0; i < substrings.size(); ++i)
QCOMPARE(device.m_paintEngine->itemFonts.at(i).second.bold(), boldnessList.at(i));
for (int i = 0; i < substrings.size(); ++i)
QCOMPARE(device.m_paintEngine->itemFonts.at(i).second.italic(), italicnessList.at(i));
for (int i = 0; i < substrings.size(); ++i)
QCOMPARE(device.m_paintEngine->itemFonts.at(i).second.pointSize(), pointSizeList.at(i));
delete w;
}
QTEST_MAIN(tst_QTextEdit)
#include "tst_qtextedit.moc"